Spring Boot Testing Best Practices
Proper testing is critical to the successful development of applications that use a microservices architecture. This guide provides some important recommendations for writing tests for Spring Boot applications, using F.I.R.S.T. principles:
- F - Fast
- I - Independent
- R - Repeatable
- S - Self-Validating
- T - Timely
Isolate the functionality to be tested
You can better isolate the functionality you want to test by limiting the context of loaded frameworks/components. Often, it is sufficient to use the JUnit unit testing framework. without loading any additional frameworks. To accomplish this, you only need to annotate your test with @Test
.
In the very naive code snippet below, there are no database interactions, and MapRepository loads data from the classpath.
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class MapRepositoryTest {
private MapRepository mapRepository = new MapRepository();
@Test
public void shouldReturnJurisdictionForZip() {
final String expectedJurisdiction = "NJ";
assertEquals(expectedJurisdiction, mapRepository.findByZip("07677"));
}
}
As a next step up in complexity, consider adding mock frameworks, like those generated by the Mockito mocking framework, if you have interactions with external resources. Using mock frameworks eliminates the need to access real instances of external resources.
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.util.Date;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@RunWith(MockitoJUnitRunner.class)
public class CarServiceTest {
private CarService carService;
@Mock
private RateFinder rateFinder;
@Before
public void init() {
carService = new CarService(rateFinder);
}
@Test
public void shouldInteractWithRateFinderToFindBestRate() {
carService.schedulePickup(new Date(), new Route());
verify(rateFinder, times(1)).findBestRate(any(Route.class));
}
}
Only load slices of functionality
@SpringBootTest Annotation
When testing spring boot applications, the @SpringBootTest
annotation loads the whole application, but it is often better to limit the application context to just the set of Spring components that participate in the test scenario. This is accomplished by listing them in the annotation declaration.
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Date;
import static org.junit.Assert.assertTrue;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {MapRepository.class, CarService.class})
public class CarServiceWithRepoTest {
@Autowired
private CarService carService;
@Test
public void shouldReturnValidDateInTheFuture() {
Date date = carService.schedulePickup(new Date(), new Route());
assertTrue(date.getTime() > new Date().getTime());
}
}
@DataJpaTest Annotation
Using @DataJpaTest
only loads @Repository
spring components, and will greatly improve performance by not loading @Service
, @Controller
, etc.
@RunWith(SpringRunner.class)
@DataJpaTest
public class MapTests {
@Autowired
private MapRepository repository;
@Test
public void findByUsernameShouldReturnUser() {
final String expected = "NJ";
String actual = repository.findByZip("07677")
assertThat(expected).isEqualTo(actual);
}
}
Running database-related tests
Sometimes, the Table Already Exists
exception is thrown when testing with an H2 database. This is an indication that H2 was not cleared between test invocations. This behavior has been observed when combining database tests with initialization of the API mocking tool WireMock. It could also occur if multiple qualifying schema-.sql files are located in the classpath.
It is a good practice to mock the beans that are involved in database interactions, and turn off Spring Boot test db initialization for the Spring profile that tests run. You should strongly consider this when testing Controllers
. Alternatively, you can try to declare your table creation DDL in schema.sql
files as CREATE TABLE IF NOT EXISTS
.
spring.datasource.initialize=false
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration
Test the web layer
Use @WebMvcTest
to test REST APIs exposed through Controllers without the server part running. Only list Controllers that are being tested.
Note: It looks like Spring Beans used by a Controller need to be mocked.
@RunWith(SpringRunner.class)
@WebMvcTest(CarServiceController.class)
public class CarServiceControllerTests {
@Autowired
private MockMvc mvc;
@MockBean
private CarService carService;
@Test
public void getCarShouldReturnCarDetails() {
given(this.carService.schedulePickup(new Date(), new Route());)
.willReturn(new Date());
this.mvc.perform(get("/schedulePickup")
.accept(MediaType.JSON)
.andExpect(status().isOk());
}
}
Keep Learning
Many of the frameworks and other capabilities mentioned in this best practices guide are described in the Spring Boot testing documentation. This recent video on testing messaging in Spring describes the use of Spock, JUnit, Mockito, Spring Cloud Stream and Spring Cloud Contract.
A more exhaustive tutorial is available to help you learn more about testing the web layer.
Frequently Asked Questions
What is Spring Boot testing?
How do you test a Spring Boot microservice?
@Test
. Alternatively, to only load slices of functionality, use the @SpringBootTest
annotation while listing the Spring components that participate in the test scenario in the annotation declaration.What are the best practices for Spring Boot testing?
How can you speed up Spring Boot testing?
@DataJpaTest
annotation to only load @Repository
Spring components. In addition, configuring the test to exclude @Service
, @Controller
, and other components will greatly improve speed.Which annotation can be used to run quick unit tests in Spring Boot?
@SpringBootTest
annotation can be used to run quick unit tests in Spring Boot.