This page describes how to write tests using the framework support in Spring Boot 1.4. It will cover unit tests that can run in isolation as well as integration tests that will bootstrap Spring context before executing tests.
The spring-boot-starter-test
is the primary dependency that contains the majority of
elements required for our tests.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
With the spring-boot-starter-test
, the following libraries will be provided.
- JUnit — The de-facto standard for unit testing Java applications.
- Spring Test & Spring Boot Test — Utilities and integration test support for Spring Boot applications.
- AssertJ — A fluent assertion library.
- Hamcrest — A library of matcher objects (also known as constraints or predicates).
- Mockito — A Java mocking framework.
- JSONassert — An assertion library for JSON.
- JsonPath — XPath for JSON.
A unit test is a test written by the programmer to verify that a relatively small piece of code is doing what it is intended to do. They are narrow in scope, they should be easy to write and execute, and their effectiveness depends on what the programmer considers to be useful. The tests are intended for the use of the programmer, they are not directly useful to anybody else, though, if they do their job, testers and users downstream should benefit from seeing fewer bugs.
Part of being a unit test is the implication that things outside the code under test are mocked or stubbed out. Unit tests shouldn't have dependencies on outside systems. They test internal consistency as opposed to proving that they play nicely with some outside system.
An integration test is done to demonstrate that different pieces of the system work together. Integration tests cover whole applications, and they require much more effort to put together. They usually require resources like database instances and hardware to be allocated for them. The integration tests do a more convincing job of demonstrating the system works (especially to non-programmers) than a set of unit tests can, at least to the extent the integration test environment resembles production.
Actually integration test gets used for a wide variety of things, from full-on system tests against an environment made to resemble production to any test that uses a resource (like a database or queue) that isn't mocked out.
Note: Ideally, it is recommended to keep the integration tests separated from the unit tests and not to run along the unit tests. It can be done by using a different profile to only run the integration tests. A couple of reasons for doing this could be that the integration tests are time-consuming and might need an actual database to execute.
The following code is a standard test case using JUnit 4. At least one @Test
is required,
and @BeforeClass
, @Before
, @After
and @AfterClass
are all optional.
public class MyTest {
@BeforeClass
public static void setUpBeforeAll() throws Exception {
// Set up once before all tests
}
@Before
public void setUp() throws Exception {
// Set up before each test
}
@Test
public void testMethod1() {
// Test method 1
}
@Test
public void testMethod2() {
// Test method 2
}
@After
public void tearDown() throws Exception {
// Tear down after each test
}
@AfterClass
public static void tearDownAfterAll() throws Exception {
// Tear down once after all tests
}
}
Spring Boot offers a lot of support for writing integration tests for your application.
@SpringBootTest
is an annotation that can be specified on a test class that runs
Spring Boot based tests. This annotation configures a complete test environment with all
your beans and everything set up.
It provides the following features over and above the regular Spring TestContext Framework:
- Uses
SpringBootContextLoader
as the defaultContextLoader
when no specific@ContextConfiguration(loader=...)
is defined. - Automatically searches for a
@SpringBootConfiguration
when nested@Configuration
is not used, and no explicit classes are specified. - Allows custom
Environment
properties to be defined using the properties attribute. - Provides support for different
webEnvironment
modes, including the ability to start a fully running container listening on a defined or random port. - Registers a
TestRestTemplate
bean for use in web tests that are using a fully running container.
A typical Spring Boot 1.4 integration test will look like this:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@TestPropertySource(locations = "classpath:application-test.properties")
public class MyTest {
// Write test cases here
}
Here’s a breakdown of what’s happening:
@RunWith(SpringRunner.class)
tells JUnit to run using Spring's testing support.@SpringBootTest
is saying "bootstrap with Spring Boot's support" (e.g. loadapplication.properties
and give me all the Spring Boot goodness).- The
webEnvironment
attribute of@SpringBootTest
can be used to configure the runtime environment. You can start tests with a MOCK servlet environment or with a real HTTP server running on either aRANDOM_PORT
or aDEFINED_PORT
. WebEnvironment.RANDOM_PORT
might be used so that the container will start at any random port. It will be helpful if several integration tests are running in parallel on the same machine.@TestPropertySource
can be used to configure locations of properties files specific to the tests. The property file loaded with@TestPropertySource
will override the existingapplication.properties
file.
@DataJpaTest
is an annotation that can be used in combination with
@RunWith(SpringRunner.class)
for a typical JPA test. It can be used when a test
focuses only* on JPA components. Using this annotation will disable full auto-configuration
and instead apply only configuration relevant to JPA tests.
By default, tests annotated with @DataJpaTest will use an embedded in-memory database
(replacing any explicit or usually auto-configured DataSource). The @AutoConfigureTestDatabase
annotation can be used to override these settings.
If you are looking to load your full application configuration, but use an embedded database,
you should consider @SpringBootTest
combined with @AutoConfigureTestDatabase
rather than this annotation.
To test the persistence layer using an embedded in-memory database with @DataJpaTest
,
the following dependency is required. The H2 DB is an in-memory database that eliminates
the need for configuring and starting an actual database for test purposes.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
@DataJpaTest
provides some standard setup needed for testing the persistence layer:
- configuring H2, an in-memory database
- setting Hibernate, Spring Data, and the DataSource
- performing an
@EntityScan
- turning on SQL logging
To carry out DB operation, some data needs to be setup in database. This can be done using
TestEntityManager
. The TestEntityManager
provided by Spring Boot is an alternative
to the standard JPA EntityManager that provides methods commonly used when writing tests.
Example:
@RunWith(SpringRunner.class)
@SpringBootTest
@DataJpaTest
public class ProductRepositoryTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private ProductRepository productRepository;
private Product product1;
private Product product2;
@Before
public void setUp() throws Exception {
product1 = new Product();
product1.setCode("P001");
product1.setName("iPhone 7");
product1.setDescription("This is an Apple phone.");
product1.setActive(true);
entityManager.persist(product1);
product2 = new Product();
product2.setCode("P002");
product2.setName("Pixel 2");
product2.setDescription("This is a Google phone.");
product2.setActive(false);
entityManager.persist(product2);
}
@Test
public void findByCode() {
Product product = productRepository.findByCode("P001");
assertNotNull(product);
assertEquals(product1, product);
}
@Test
public void findByCode_NotFound() {
Product product = productRepository.findByCode("P003");
assertNull(product);
}
}
@DataMongoTest
is an annotation that can be used in combination with
@RunWith(SpringRunner.class)
for a typical MongoDB test. It can be used when
a test focuses only on MongoDB components. Using this annotation will disable full
auto-configuration and instead apply only configuration relevant to MongoDB tests.
By default, tests annotated with @DataMongoTest
will use an embedded in-memory
MongoDB process (if available).
To test the persistence layer using an embedded in-memory MongoDB with @DataMongoTest
,
the following dependency is required.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
</dependency>
Example:
@RunWith(SpringRunner.class)
@SpringBootTest
@DataMongoTest
public class StudentRepositoryTest {
@Autowired
private MongoTemplate mongoTemplate;
@Autowired
private StudentRepository studentRepository;
private Student student1;
private Student student2;
@Before
public void setUp() throws Exception {
student1 = new Student();
student1.setName("Jack Bauer");
student1.setGrade(6);
mongoTemplate.save(student1);
student2 = new Student();
student2.setName("Tony Almeida");
student2.setGrade(4);
mongoTemplate.save(student2);
}
@Test
public void findByName() {
List<Student> students = studentRepository.findByName("Jack Bauer");
assertEquals(1, students.size());
assertEquals(student1, students.get(0));
}
@Test
public void findByName_NotFound() {
List<Student> students = studentRepository.findByName("Chloe O'Brian");
assertEquals(0, students.size());
}
}
Spring Boot includes a @MockBean
annotation that can be used to define a Mockito mock
for a bean inside your ApplicationContext
. You can use the annotation to add new beans,
or replace a single existing bean definition. Additionally you can also use @SpyBean
to wrap any existing bean with a Mockito spy.
To check the Service class, we need to have an instance of Service class created and available
as a @Bean
so that we can @Autowire
it in our test class. This configuration is
achieved by using the @TestConfiguration
annotation.
Example:
@RunWith(SpringRunner.class)
public class ProductServiceTest {
@TestConfiguration
static class ProductServiceTestConfiguration {
@Bean
public ProductService productService() {
return new ProductService();
}
}
@Autowired
private ProductService productService;
@MockBean
private ProductRepository productRepository;
private Product product;
@Before
public void setUp() throws Exception {
product = new Product();
product.setId(1L);
product.setCode("P001");
product.setName("Product 1");
product.setDescription("Test is a cool product.");
product.setActive(true);
}
@Test
public void findProductByCode() {
when(productRepository.findByCode(anyString())).thenReturn(product);
Product found = productService.findProductByCode("P001");
assertThat(found).isEqualTo(product);
verify(productRepository, times(1)).findByCode(anyString());
verifyNoMoreInteractions(productRepository);
}
}
In many cases, unit tests can be performed using Mockito without any Spring test features.
@Mock
creates a mock. @InjectMocks
creates an instance of the class and injects
the mocks that are created with the @Mock
(or @Spy
) annotations into this instance.
Note that @RunWith(MockitoJUnitRunner.class)
or Mockito.initMocks(this)
must be
used to initialize these mocks and inject them.
Example:
@RunWith(MockitoJUnitRunner.class)
public class ProductServiceTest {
@InjectMocks
private ProductService productService;
@Mock
private ProductRepository productRepository;
private Product product;
@Before
public void setUp() throws Exception {
product = new Product();
product.setId(1L);
product.setCode("P001");
product.setName("Product 1");
product.setDescription("Test is a cool product.");
product.setActive(true);
}
@Test
public void findProductByCode() {
when(productRepository.findByCode(anyString())).thenReturn(product);
Product found = productService.findProductByCode("P001");
assertThat(found).isEqualTo(product);
verify(productRepository, times(1)).findByCode(anyString());
verifyNoMoreInteractions(productRepository);
}
}
@WebMvcTest
can be used to auto-configure the Spring MVC infrastructure for Controller
unit tests. In most of the cases, @WebMvcTest
will be limited to bootstrap a single controller. It is used along with @MockBean
to provide mock
implementations for required dependencies.
@WebMvcTest
also auto-configures MockMvc
which offers a powerful way of easy
testing MVC controllers without starting a full HTTP server.
Example:
@RunWith(SpringRunner.class)
@WebMvcTest(ProductController.class)
public class ProductControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private ProductService productService;
private Product product;
@Before
public void setUp() throws Exception {
product = new Product();
product.setId(1L);
product.setCode("P001");
product.setName("Product 1");
product.setDescription("This is a cool product.");
product.setActive(true);
}
@Test
public void findProductById() throws Exception {
when(productService.findProductById(anyLong())).thenReturn(product);
mockMvc.perform(get("/products/1").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$.id", is(1)))
.andExpect(jsonPath("$.code", is("P001")))
.andExpect(jsonPath("$.name", is("Product 1")))
.andExpect(jsonPath("$.description", is("This is a cool product.")))
.andExpect(jsonPath("$.active", is(true)));
verify(productService, times(1)).findProductById(anyLong());
verifyNoMoreInteractions(productService);
}
}
Also Controllers can be tested using Mockito without the help of @WebMvcTest
.
In this case, all mocks created by @Mock
should be injected to an instance of the class
using @InjectMocks
. Note that @RunWith(MockitoJUnitRunner.class)
or Mockito.initMocks(this)
must be used to initialize these mocks and inject them.
There are two ways to configure MockMvc
:
MockMvcBuilders.webAppContextSetup(webApplicationContext).build()
MockMvcBuilders.standaloneSetup(controller).build()
The first approach will automatically load the Spring configuration and inject WebApplicationContext
into the test. The second approach does not load the Spring configuration.
Example:
@RunWith(MockitoJUnitRunner.class)
public class ProductControllerMockitoTest {
@InjectMocks
private ProductController productController;
@Mock
private ProductService productService;
private MockMvc mockMvc;
private Product product;
@Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.standaloneSetup(productController).build();
product = new Product();
product.setId(1L);
product.setCode("P001");
product.setName("Product 1");
product.setDescription("This is a cool product.");
product.setActive(true);
}
@Test
public void findProductById() throws Exception {
when(productService.findProductById(anyLong())).thenReturn(product);
mockMvc.perform(get("/products/1").contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(jsonPath("$.id", is(1)))
.andExpect(jsonPath("$.code", is("P001")))
.andExpect(jsonPath("$.name", is("Product 1")))
.andExpect(jsonPath("$.description", is("This is a cool product.")))
.andExpect(jsonPath("$.active", is(true)));
verify(productService, times(1)).findProductById(anyLong());
verifyNoMoreInteractions(productService);
}
}
@RestClientTest
can be used in combination with @RunWith(SpringRunner.class)
for a typical
Spring rest client test. Can be used when a test focuses only on beans that use RestTemplateBuilder
.
Using this annotation will disable full auto-configuration and instead apply only configuration relevant
to rest client tests (i.e. Jackson or GSON auto-configuration and @JsonComponent
beans, but not
regular @Component
beans).
By default, tests annotated with RestClientTest will also auto-configure a MockRestServiceServer
.
For more fine-grained control the @AutoConfigureMockRestServiceServer
annotation can be used.
Example:
SomeRestClient.java
@Component
public class SomeRestClient {
private RestTemplate restTemplate;
@Autowired
public SomeRestClient(RestTemplateBuilder restTemplateBuilder) {
restTemplate = restTemplateBuilder.build();
}
public String getSomeStringFromRemote() {
ResponseEntity<String> response = restTemplate
.getForEntity("http://remote.domain.com/some/endpoint", String.class);
// Some logic goes here...
}
}
SomeRestClientTest.java
@RunWith(SpringRunner.class)
@RestClientTest(SomeRestClient.class)
public class SomeRestClientTest {
private static String ENDPOINT_URL = "http://remote.domain.com/some/endpoint";
@Autowired
private SomeRestClient client;
@Autowired
private MockRestServiceServer server;
@Rule
public ExpectedException exception = ExpectedException.none();
@Test
public void getSomethingFromRemote_OK() {
this.server
.expect(requestTo(ENDPOINT_URL))
.andRespond(withSuccess("OK", MediaType.APPLICATION_JSON));
String response = client.getSomethingFromRemote();
assertNotNull(response);
assertEquals("OK", response);
}
@Test
public void getSomethingFromRemote_ServerError() throws Exception {
exception.expect(RestClientException.class);
exception.expectMessage(Pattern.compile("Failed to get").pattern());
this.server
.expect(requestTo(ENDPOINT_URL))
.andRespond(withStatus(HttpStatus.INTERNAL_SERVER_ERROR));
String response = client.getSomethingFromRemote();
}
}
If you are testing a bean that doesn't use RestTemplateBuilder
but instead injects a RestTemplate
directly, you can add @AutoConfigureWebClient(registerRestTemplate=true)
.
OtherRestClient.java
@Component
public class OtherRestClient {
@Autowired
private RestTemplate restTemplate;
public String getSomethingFromRemote() {
ResponseEntity<String> response = restTemplate
.getForEntity("http://remote.domain.com/some/endpoint", String.class);
// Some logic goes here...
}
}
OtherRestClientTest.java
@RunWith(SpringRunner.class)
@RestClientTest(OtherRestClient.class)
@AutoConfigureWebClient(registerRestTemplate = true)
@Slf4j
public class OtherRestClientTest {
@Autowired
private OtherRestClient client;
@Autowired
private MockRestServiceServer server;
// Some tests go here...
}
As each class has its own test class, name the TestCase class after the class being tested, following the naming conventions below.
- {ClassName}Test
- {FeatureBeingTested}Tests — Only for multi-featured integration tests or test suites.
Examples:
ProductService
— A target class under testProductServiceTest
— A test case class
Note: It is recommended to locate a test class in the same package with the target class, so that it will help minimize the access level of test methods.
Test method names should follow one of the naming conventions below.
- MethodName_ExpectedBehavior_StateUnderTest
- test{FeatureBeingTested}
Examples:
postMessage_InternalServerError
withdrawMoney_ThrowsException_IfAccountIsInvalid
testCRUD