Thursday, December 31, 2020

Testing in Spring Boot 2

 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.

pom.xml
<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.

pom.xml
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.

SpringRunner example
@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.

MockitoJUnitRunner example
@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.

SpringExtension example
//@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.

MockitoExtension example
@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 example
@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 example
@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.

@WebMvcTest example
@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 @JsonComponentJacksonTesterJsonbTester and GsonTester 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 for MockRestServiceServer.

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 test config
@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 test config
@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

How to DROP SEQUENCE in Oracle?

  Oracle  DROP SEQUENCE   overview The  DROP SEQUENCE  the statement allows you to remove a sequence from the database. Here is the basic sy...