Integration Tests With Spring

Unit tests of your project created with Spring, you should not depend on Spring, as unit tests must not depend on external dependencies and Spring is an external dependency. These dependencies should be stubbed or mocked instead.

However, Spring provides support for integration testing with configurations, profiles and databases. These support-classes are located in spring-test.jar and are used in combination with JUnit.

Setup

@RunWith(SpringJUnit4ClassRunner.class)

annotation on your testclass creates an application context which is shared for all test methods.

If a test method modifies beans in the application context it can be annotated with

@DirtiesContext

to clean the context after the test execution.

Configurations

To reference a configuration for the tests, the testclass can be annotated with

@ConxtextConfiguration(classes=TestConfig.class)

With that in place, you can inject the bean to be tested into your test class using

@Autowired

Example

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=TestConfig.class)
public class MyServiceTest {
 
    @Autowired
    private MyService myService; //bean to be tested
 
    @Test
    public void testMyService() {
        myService.doSomething();   
    }
}

@ContextConfiguration could also be supplied with a string array to reference XML configurations. Without any argument, it defaults to

[classname]-context.xml

in the same package.

As a third option, you can add a test-specific configuration directly in the testclass by defining an static inner class in the test class and annotate it with

@Configuration

Profiles

Testing with profiles can be done by annotating the test class with

@ActiveProfiles({"profile_1","profile_2"})

Beans associated to one of the defined profiles and those not associated to any profile,
will be loaded automatically.

Testing with databases

When you run tests against an in-memory-database you can use

@Sql

ensure its proper state or to insert some testrecords.

@Sql can be supplied with string arguments referencing sql-scripts.

If the annotation decorates a class, the script(s) run before every test method but it can also decorate a single test method.

Also, multiple @Sql annotations can be used and there is a possibility, to set an

executionPhase

e.g. to run a specific script as cleanup after a certain method.

If no string argument is defined with the annotation, the script will be referenced as

[classname].[methodname].sql

Defining

config=@SqlConfig(...)

as argument of @Sql gives you a whole lot of other options to control the scripts, e.g. if the test should fail, if the execution of the script fails.

In-memory databases

Create a DataSource bean of an

EmbeddedDatabaseBuilder

where you define a name, a type (HSQL, H2 or Derby) and can add several scripts with

.addScript("classpath:testdb-schema.sql")

to set it up. Finally, call

.build();

The XML-equivalent is

<jdbc:embedded-database id="..." type="...">
    <jdbc:script location="..." />
</jdbc:embedded-database>

Example

Testclass:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={UseradminConfiguration.class, TestRepositoryConfiguration.class})
public class UserServiceTest {
 
    @Autowired
    private UserService userService;
    
    @Test
    @Sql(scripts="classpath:testdata.sql", executionPhase=ExecutionPhase.BEFORE_TEST_METHOD)
    public void testUserRetrieval() {
        User testUser = userService.getUserById(1);
        assertTrue("test".equals(testUser.getFirstName()) && "user".equals(testUser.getLastName()));
    }
}

Testconfiguration:

(Depending on the implementation of service and repository, the only bean needed could be the data-source).

@Configuration
public class TestRepositoryConfiguration {
 
    @Bean(name = "dataSource")
    public DataSource dataSource() {
 
        EmbeddedDatabaseBuilder dataSource = new EmbeddedDatabaseBuilder();
        dataSource.setType(EmbeddedDatabaseType.H2);
        dataSource.addScript("classpath:schema.sql");
        return dataSource.build();
    }
    
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws ClassNotFoundException, PropertyVetoException {
        LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
        emf.setDataSource(dataSource());
        emf.setJpaProperties(new Properties());
        emf.setJpaVendorAdapter(jpaAdapter());
        emf.setPackagesToScan("ch.pma.useradmin.entities");
        return emf;
    }
 
    private JpaVendorAdapter jpaAdapter() {
        return new HibernateJpaVendorAdapter();
    }
    
    @Bean
    public JpaTransactionManager transactionManager() {
        return new JpaTransactionManager();
    }
}

schema.sql

DROP TABLE IF EXISTS USER;
CREATE TABLE USER (id INTEGER IDENTITY PRIMARY KEY, first_name VARCHAR(50), last_name VARCHAR(50) NOT NULL, STATUS INT(11), ROLE VARCHAR(50));

testdata.sql

INSERT INTO USER(first_name, last_name, STATUS, ROLE) VALUES('test', 'user', 1, 'admin');

Using an existing test database

You need a

DataSourceInitalizer

where you set the dataSource pointing to the database and a

DatabasePopulator

(e.g. ResourceDatabasePopulator) which you provide with the paths to the initialization scripts.
The same in XML is more compact:

<jdbc:initialize-database data-source="...">
    <jdbc:script location="..." />
<jdbc:initialize>

Bing Bong

Guest Author - Knows Java and Spring very well.

Latest posts by Bing Bong (see all)

Leave a Reply

Your email address will not be published. Required fields are marked *

*