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>