Learn to write unit and integration tests in spring boot applications. Learn the difference between unit tests and integration tests along with annotations which support such tests.
1. Unit vs Integration Tests
Typically any software application is divided into different modules and components. When one such component is tested in isolation, it is called unit testing. It is written to verify that a relatively small piece of code is doing what it is intended to do.
Unit tests do not verify whether the application code works with external dependencies correctly. It focuses on single component and mocks all dependencies this component interacts with.
Once different modules are developed and integrated then Integration testing is carried out. It’s main purpose is to discover the issues when different modules interact with each other to process user requests end to end.
Integration tests can put whole application in scope or only certain components – based on what is being tested. They may need to require resources like database instances and hardware to be allocated for them. Though these interactions can be mocked out as well to improve the test performance.
In terms of typical Spring boot crud application, unit tests can be written to test REST controllers, DAO layer etc separately. It will not require even the embedded server as well.
In integration testing, we shall focus on testing complete request processing from controller to persistence layer. Application shall run inside embedded server to create application context and all beans. Some of these beans may be overridden to mock certain behaviors.
2. Dependencies
2.1. Junit 4 Tests (Default)
To write tests in spring boot applications, the best way is include spring-boot-starter-test in pom.xml
file. It brings Junit 4, AssertJ, Hamcrest, Mockito, JSONassert and JsonPath dependencies into application with test scope.
< dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-test</ artifactId > < scope >test</ scope > </ dependency > |
2.2. Junit 5 Tests
Spring boot supports Junit 5 tests as well. To use Junit 5, include it’s dependency and exclude Junit 4 from spring-boot-starter-test.
A embedded database dependency comes handy while writing integration tests.
dependency> < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-test</ artifactId > < scope >test</ scope > <!-- exclude junit 4 --> < exclusions > < exclusion > < groupId >junit</ groupId > < artifactId >junit</ artifactId > </ exclusion > </ exclusions > </ dependency > <!-- junit 5 --> < dependency > < groupId >org.junit.jupiter</ groupId > < artifactId >junit-jupiter-api</ artifactId > < scope >test</ scope > </ dependency > < dependency > < groupId >org.junit.jupiter</ groupId > < artifactId >junit-jupiter-engine</ artifactId > < scope >test</ scope > </ dependency > < dependency > < groupId >com.h2database</ groupId > < artifactId >h2</ artifactId > < scope >test</ scope > < version >1.4.194</ version > </ dependency > |
3. Test runners
Tests written in spring boot can be run in variety of ways. Let’s see few most common ways.
3.1. @RunWith(SpringRunner.class) – [ Junit 4 ]
By default, tests written are in Junit 4. To run such tests, we can use SpringRunner class (extends SpringJUnit4ClassRunner) with @RunWith
annotation at class level.
@RunWith (SpringRunner. class ) @WebFluxTest (controllers = EmployeeController. class ) public class EmployeeRestControllerTest { //tests } |
3.2. @RunWith(MockitoJUnitRunner.class) – [ Junit 4 with Mockito ]
It tests use @Mock
objects the prefer using MockitoJUnitRunner. It initializes mocks annotated with Mock, so that explicit usage of MockitoAnnotations.initMocks(Object)
is not necessary. Mocks are initialized before each test method.
@RunWith (MockitoJUnitRunner. class ) public class EmployeeRestControllerTest { @Mock private Repository repository; } |
3.3. @ExtendWith(SpringExtension.class) – [ Junit 5 ]
SpringExtension integrates the Spring TestContext Framework into JUnit 5’s Jupiter programming model.
//@ExtendWith(SpringExtension.class) // included in @WebFluxTest @WebFluxTest (controllers = EmployeeController. class ) @Import (EmployeeService. class ) public class EmployeeControllerTest { // } |
3.4. @ExtendWith(MockitoExtension.class) – [ Junit 5 ]
MockitoExtension initializes mocks and handles strict stubbings. It is equivalent of the MockitoJUnitRunner
.
Most test annotations include this annotation with them so no need to include it explicitly.
@ExtendWith (MockitoExtension. class ) public class EmployeeControllerTest { // } |
4. Spring boot *Test annotations
Spring boot provides various annotations to enable test infrastructure related to only certain part of application. It also provides annotations which help in integration testing as well. Let’s visit them.
4.1. @SpringBootTest
This annotation helps in writing integration tests. It starts the embedded server and fully initializes the application context. We can inject the dependencies in test class using @Autowired
annotation.
We can also provide test specific beans configuration using nested @Configuration class or explicit @TestConfiguration classes.
It also provides support for different webEnvironment modes and running web server listening on a defined or random port. It also registers a TestRestTemplate and/or WebTestClient bean for use in web tests.
@SpringBootTest (classes = SpringBootDemoApplication. class , webEnvironment = WebEnvironment.RANDOM_PORT) public class EmployeeControllerIntegrationTests { @LocalServerPort private int port; @Autowired private TestRestTemplate restTemplate; //tests } |
Read More : @SpringBootTest example
4.2. @WebMvcTest
This annotation is used for Spring MVC tests. It disables full auto-configuration and instead apply only configuration relevant to MVC tests.
It also auto-configure MockMvc instance as well. We can initialize only one web controller by passing .class
as the annotation attribute.
@WebMvcTest (EmployeeRESTController. class ) public class TestEmployeeRESTController { @Autowired private MockMvc mvc; // } |
4.3. @WebFluxTest
This annotation disables full auto-configuration and instead apply only configuration relevant to WebFlux tests. By default, tests annotated with @WebFluxTest will also auto-configure a WebTestClient.
Typically @WebFluxTest is used in combination with @MockBean or @Import
to create any collaborators required by the controller bean.
@WebFluxTest (controllers = EmployeeController. class ) @Import (EmployeeService. class ) public class EmployeeControllerTest { @MockBean EmployeeRepository repository; @Autowired private WebTestClient webClient; //tests } |
4.4. Other frequently used annotations
- @JdbcTest – can be used for a typical jdbc test when a test focuses only on jdbc-based components. It disables full auto-configuration and instead apply only configuration relevant to jdbc tests.
By default, tests annotated with @JdbcTest are transactional and roll back at the end of each test. The annotation configures an in-memory embedded database and JdbcTemplate.
- @JooqTest – It can be used when a test focuses only on jOOQ-based components. Beware that by default, tests annotated with @JooqTest use the application configured database. To use embedded in-memory database, @AutoConfigureTestDatabase annotation can be used to override these settings.
- @JsonTest – It is used when a test focuses only on JSON serialization. It initializes the
@JsonComponent
,JacksonTester
,JsonbTester
andGsonTester
fields. - @DataJpaTest – It can be used to test JPA applications. By default, it scans for @Entity classes and configures Spring Data JPA repositories. If an embedded database is available on the classpath, it configures one as well.
By default, data JPA tests are transactional and roll back at the end of each test.
Data JPA tests may also inject a TestEntityManager bean, which provides an alternative to the standard JPA
EntityManager
that is specifically designed for tests. - @DataMongoTest – is used to test MongoDB applications. By default, it configures an in-memory embedded MongoDB (if available), configures a MongoTemplate, scans for
@Document
classes, and configures Spring Data MongoDB repositories. - @DataRedisTest – is used to test Redis applications. By default, it scans for @RedisHash classes and configures Spring Data Redis repositories.
- @DataLdapTest – is used to test LDAP applications. By default, it configures an in-memory embedded LDAP (if available), configures an LdapTemplate, scans for @Entry classes, and configures Spring Data LDAP repositories.
- @RestClientTest – is used to test REST clients. By default, it auto-configures Jackson, GSON, and Jsonb support, configures a
RestTemplateBuilder
, and adds support forMockRestServiceServer
.
5. Test Configuration
@TestConfiguration is specialized form of @Configuration
that can be used to define additional beans or customizations for a test.
In spring boot, any beans configured in a top-level class annotated with @TestConfiguration
will not be picked up via component scanning. We must explicitly register the @TestConfiguration class with the class that contains the test cases.
The best thing is that these test configurations are not automatically part of application’s primary configuration. They are available only on-demand using one of below two ways to include this additional test configuration i.e.
5.1. @Import annotation
It call be used to import one or more configuration classes into application context or spring test context.
@Import (MyTestConfiguration. class ) @SpringBootTest (webEnvironment = WebEnvironment.RANDOM_PORT) public class SpringBootDemoApplicationTests { @LocalServerPort int randomServerPort; @Autowired DataSource datasource; //tests } |
5.2. Static nested classes
We can define the test configurations in nested classes inside the test class. The nested class can be annotated with @Configuration or @TestConfiguration annotations.
- In case of nested
@Configuration
class, the given configuration would be used “instead of” the application’s primary configuration. - A nested
@TestConfiguration
class is used “in addition to” the application’s primary configuration.
@Import (MyTestConfiguration. class ) @SpringBootTest (webEnvironment = WebEnvironment.RANDOM_PORT) public class SpringBootDemoApplicationTests { @LocalServerPort int randomServerPort; @Autowired DataSource datasource; //tests } |
6. Mocking
Spring boot has an excellent support for mocking the dependencies with or without using Mockito.
6.1. With Mockito – @Mock
@Mock is used for mock creation. It makes the test class more readable. In test class, to process mockito annotations, MockitoAnnotations.initMocks(testClass)
must be used at least once.
Please note that if you are using RunWith(MockitoJUnitRunner.class)
then explicit usage of MockitoAnnotations.initMocks() is not necessary. Mocks are initialized before each test method.
Use @Mock
in unit testing where spring text context is not needed.
6.2. Without Mockito – @MockBean
@MockBean annotation used to add mocks to a Spring ApplicationContext. It allow to mock a class or an interface and to record and verify behaviors on it.
Interesting this is that any existing bean of the same type defined in the context will be replaced by the mock. If no existing bean is defined a new one will be added.
@MockBean
is similar to mockito’s @Mock
but with Spring support. We will generally use @MockBean
along with either @WebMvcTest
or @WebFluxTest
annotations. These annotations are for web test slice and limited to a single controller.
In given example, we are mocking the EmployeeRepository
bean. In this way, all the application code will be invoked but all repository interactions will be mocked.
@WebFluxTest (controllers = EmployeeController. class ) @Import (EmployeeService. class ) public class EmployeeControllerTest { @MockBean EmployeeRepository repository; @Autowired private WebTestClient webClient; //tests } |
7. Conclusion
Spring boot provides excellent support for unit testing and integration testing of applications and it’s various modules. We shall use the provided support through the use of annotations – very carefully.
Use @SpringBootTest
annotation for integration testing while other auto-configuration annotations for unit testing of specific components.
Mocking the certain behavior is very common requirement and we can use either mockito’s @Mock or Spring’s @MockBean annotation for this purpose.
Drop me your questions in comments section.
No comments:
Post a Comment