Test driven development is a buzz-word nowadays in the software development community. However many developers do not do a very good job in this area. That's a separate topic. In this article, I will explain how we can test different components of a Spring Application.
The client layer is responsible to call external services from our application.
The data layer is responsible to connect to various databases and get/persist data for us.
Actually there can be other layers as well such as business layer where some validations might be done, but writing unit tests for those components is pretty straight forward. Just test each method, mock any external dependencies with Mockito, or Powermock etc and cover the scenarios.
Normally, developers used the @SpringBootTest annotation to test all the components. But this annotation initializes the whole application context and if there are large number of test classes, using this annotation could be time consuming as you are configuring the application context multiple and this is an expensive operation.
Spring provides a number of annotations to test different layers of the application. When you use the layer specific test, the whole context is not created and the tests run pretty faster.
Annotations provided by Spring:
The next annotation is @RestClientTest, which can be used if you want to test the REST clients. By default, if configures Jackson and Gson support, configure a RestTemplateBuilder and add support for MockRestServiceServer (intercepts the http call).
Sample code:
DataJpaTest annotation is used to test JPA applications. By default it configures in-memory embedded database, scan for @Entity classes and configure SpringDataJPA repositories. The Data JPA tests have transactional and rollback at the end of each test by default.
Sample code:
Also there are some other best practices that you might wanna use.
When you need the whole application context to be initialized, you can configure spring to use a random port.
The random port can be accessed inside your code by following.
Enjoy Test Driven Development !!!
References:
There are basically three different components in a Spring Application.
- Controller Layer
- Client Layer
- Data Layer
The client layer is responsible to call external services from our application.
The data layer is responsible to connect to various databases and get/persist data for us.
Actually there can be other layers as well such as business layer where some validations might be done, but writing unit tests for those components is pretty straight forward. Just test each method, mock any external dependencies with Mockito, or Powermock etc and cover the scenarios.
When you create a SpringBoot application, by default spring creates a test class with following annotations
@RunWith(SpringRunner.class) @SpringBootTest
Normally, developers used the @SpringBootTest annotation to test all the components. But this annotation initializes the whole application context and if there are large number of test classes, using this annotation could be time consuming as you are configuring the application context multiple and this is an expensive operation.
Spring provides a number of annotations to test different layers of the application. When you use the layer specific test, the whole context is not created and the tests run pretty faster.
Annotations provided by Spring:
- @SpringBootTest
- @WebMvcTest
- @RestClientTest
- @DataJpaTest
- @JsonTest
- @DataMongoTest
Sample code:
@RunWith(SpringRunner.class) @WebMvcTest(MyController.class) @ActiveProfiles("unittest") @Category(UnitTest.class) public class MySampleTest { @Autowired private MockMvc mockMvc; @MockBean private MyAsyncService mySvc; @MockBean private MyServiceClient myClient; @MockBean private RestTemplate restTemplate; @Test public void test_pageNotFound() throws Exception { MockHttpServletResponse response = this.mockMvc.perform(post("/my-endpoint/1234")) .andReturn() .getResponse(); assertThat(response.getStatus()).isNotEqualTo(404); }
...
The next annotation is @RestClientTest, which can be used if you want to test the REST clients. By default, if configures Jackson and Gson support, configure a RestTemplateBuilder and add support for MockRestServiceServer (intercepts the http call).
Sample code:
@RunWith(SpringRunner.class) @RestClientTest(RemoteVehicleDetailsService.class) public class ExampleRestClientTest { @Autowired private RemoteVehicleDetailsService service; @Autowired private MockRestServiceServer server; @Test public void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() throws Exception { this.server.expect(requestTo("/greet/details")) .andRespond(withSuccess("hello", MediaType.TEXT_PLAIN)); String greeting = this.service.callRestService(); assertThat(greeting).isEqualTo("hello"); } }
DataJpaTest annotation is used to test JPA applications. By default it configures in-memory embedded database, scan for @Entity classes and configure SpringDataJPA repositories. The Data JPA tests have transactional and rollback at the end of each test by default.
Sample code:
@RunWith(SpringRunner.class) @DataJpaTest
@Transactional( propagation=Propagation.NOT_SUPPORTED) public class ExampleRepositoryTests { @Autowired private TestEntityManager entityManager; @Autowired private UserRepository repository; @Test public void testExample() throws Exception { this.entityManager.persist(new User("sboot", "1234")); User user = this.repository.findByUsername("sboot"); assertThat(user.getUsername()).isEqualTo("sboot"); assertThat(user.getVin()).isEqualTo("1234"); } }
Also there are some other best practices that you might wanna use.
When you need the whole application context to be initialized, you can configure spring to use a random port.
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
The random port can be accessed inside your code by following.
@LocalServerPort private int port;
Enjoy Test Driven Development !!!
References:
https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html