Best Practices for How to Test Spring Boot Applications
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 static org.junit.Assert.assertEquals;
import org.junit.Test;
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 static org.mockito.Matchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.Date;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
@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 static org.junit.Assert.assertTrue;
import java.util.Date;
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;
@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?
Spring Boot testing is the practice of ensuring the functionality of Spring Boot applications and their respective components, a critical step in the successful development of applications in a microservices architecture.
How do you test a Spring Boot microservice?
Spring Boot microservices can be tested by using the JUnit testing framework, by annotating the test with @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?
For Spring Boot testing, it is best practice to utilize the F.I.R.S.T. principles. Therefore, the test must be fast, independent, repeatable, self-validating, and timely.
How can you speed up Spring Boot testing?
Spring Boot testing can be optimized by using the @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?
The @SpringBootTest
annotation can be used to run quick unit tests in Spring Boot.
What are the benefits of using mock frameworks in Spring Boot testing?
Mock frameworks eliminate the need to access real instances of external resources while Spring Boot testing.