Spring Boot provides us with different functionalities and facilities to perform both unit and integration tests in our applications or services. In this post about examples of testing in Spring Boot, we will see how we can perform testing using the annotations and functionalities provided by Spring Boot.
If you need to configure a different database for your tests, you can take a look at Testcontainers with Spring Boot.
What are integration tests?
We could define integration tests as that part of testing in our applications where the modules are logically integrated and tested as a group. Generally, in our applications or services, we will have different modules or layers. The objective of this type of test is to see and analyze possible defects and errors in the interaction between the different layers and see how they integrate with each other.
What are unit tests?
We could define a unit test as the test that will allow us to verify a unit or part of our code. That is, testing and verifying isolated parts of our code to ensure that the functionality we have implemented works correctly.
Integration Test vs Unit Tests
A unit test will analyze or verify an isolated functionality, without interaction with other modules or with other integrations. The main difference with an Integrated Test is basically that with the latter, we are going to perform a test and verify the system as a whole, to detect possible errors in the integrations.
We could also say that with an integrated test, we can analyze different functionalities, and when reaching and covering different modules, tests on unit test functionalities could be performed.
Maven dependencies for Integrated Tests in Spring Boot
Let’s start with examples of Testing in Spring Boot.
The essential library that our Spring Boot applications will need to perform testing is spring-boot-starter-test.
This library will add the functionalities we need to perform testing in our application.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
Integration Test in Spring Boot with @SpringBootTest
When we create our integration tests, the best approach will be to separate them from the unit tests and create them in separate classes.
Spring Boot provides us with the @SpringBootTest annotation to be able to initialize our application based on some properties that we provide.
This annotation will allow us to create an ApplicationContext of our application. Let’s see an example:
@ExtendWith(SpringExtension.class) @SpringBootTest @AutoConfigureMockMvc public class BookControllerIT { @Autowired private MockMvc mockMvc;
In the creation of the above class, we have added several annotations that will facilitate the creation of our integration test:
- SpringBootTest: It will create a container of our application by creating an ApplicationContext.
- ExtendWith for JUnit5: To enable support to make use of JUnit 5.
- AutoconfigureMockMvc: It will allow us to use MockMvc for our integrated tests so that we can make HTTP requests.
As we can see, the @SpringBootTest annotation starts our application with an ApplicationContext for testing. This annotation can go alone or with other parameters, for example:
- webEnvironment = WebEnvironment.RANDOM_PORT: With this option, our tests will start on a random port that is not already in use.
- webEnvironment = WebEnvironment.DEFINED_PORT: By including this option, our tests will start on a predefined port.
If you want to know on which port your application is starting, you can use @LocalServerPort.
Modifications and AutoConfigurations for our ApplicationContext
The use of @SpringBootTest allows us to include different auto-configurations for our tests (we have already seen some in the previous point), let’s see what we can do in our tests:
Enabling auto-configurations
The use of SpringBootTest for our tests allows us to create different auto-configurations, let’s show some of the ones we can add:
- @AutoconfigureMockMvc: It allows us to add a MockMvc to our ApplicationContext, so we can make HTTP requests against our controller.
- @AutoConfigureTestDatabase: Generally, we will have an embedded or in-memory database, but this annotation allows us to test against a real database.
- @JsonTest: By using this annotation, we can verify that our JSON serializers and deserializers work correctly.
- @RestClientTest: It allows us to verify our RestTemplate, and together with MockRestServiceServer, it allows us to mock the responses that come from the RestTemplate.
- @AutoConfigureWebTestClient: It allows us to verify the server’s endpoints and adds WebTestClient to the context.
These are some of the most common ones that are usually applied in integration tests with Spring Boot, but there are many more. You can take a look at them here if you want.
Configurations in tests through Properties Files with ActiveProfiles
Just like we do in our Spring Boot application by using different profiles through our properties, for example application-dev.yml, in our integrated tests, we can also load specific properties. To be able to use this functionality, we can add the @ActiveProfiles annotation.
This annotation should have an associated properties file, for example, application-test.yml, and our @ActiveProfiles will be associated with test.
# application-test.yml hi: hi
@SpringBootTest @ActiveProfiles("test") class HiControllerIntegrationTest{ @Value("${hi}") String sayHi; @Test void test(){ assertThat(sayHi).isEqualTo("hi"); } }
Overwriting Configuration Properties
The use of ActiveProfiles will help us to load an entire properties file, but in many cases, we will only need to overwrite a property in our file. For those cases, we can add “properties” and the field to be overwritten along with our @SpringBootTest annotation:
# application.yml hi: hi
@SpringBootTest(properties = "hi=bye") class HiControllerIntegrationTest{ @Value("${hi}") String sayHi; @Test void test(){ assertThat(hi).isEqualTo("bye"); } }
As our application.yml has that property, we will overwrite it using “Properties”.
Modifying Configuration Properties with TestPropertySource
Another way to load properties in our ApplicationContext is by using @TestPropertySource, so we can load a customized file for our tests. Let’s see an example:
# src/test/resources/hi.properties hi=hi bye=bye
@SpringBootTest @TestPropertySource(locations = "/hi.properties") class HiControllerIntegrationTest { @Value("${hi}") String hi; @Test void test(){ assertThat(hi).isEqualTo("hi"); } }
Integration Testing with @DataJpaTest
The @DataJpaTest annotation allows us to perform integration testing for our JPA applications. This annotation scans by default those classes annotated with @Entity and JPA repositories, and also configures the embedded database for our tests.
By default, @DataJapTest makes our tests transactional and rolls back at the end of the test.
@Entity @Getter @Setter public class Book { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String title; private int price; private String isbn; }
@Repository public interface BookRepository extends JpaRepository<Book, Long> { }
@DataJpaTest public class BookControllerDataJpaTestIT { @Autowired private TestEntityManager entityManager; @Autowired private BookRepository bookRepository; @Test public void whenFindByName_thenReturnBook() { // given Book book = createBook(); entityManager.persist(book); entityManager.flush(); // when Book b = bookRepository.findByTitle("The Count of Monte Cristo").orElseThrow(); // then assertThat(b.getTitle()) .isEqualTo(book.getTitle()); } private Book createBook() { Book book = new Book(); book.setIsbn("1A2s-3f"); book.setTitle("The Count of Monte Cristo"); book.setPrice(34); return book; } }
The use of TestEntityManager is an alternative to the use of JPA EntityManager to use JPA methods in our tests.
In the previous example, we save to the database and then retrieve it to verify that the result is correct.
Integration Testing with WebTestClient
One of the functionalities that Spring 5 brought was the incorporation of WebFlux to provide us with support for reactive programming. For those scenarios in which we use WebFlux in our application, it will be necessary to use WebTestClient to test our API.
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) public class WebTestClientExampleTests { @Autowired private WebTestClient webClient; @Test public void exampleTest() { this.webClient.get().uri("/books/1").exchange().expectStatus().isOk() .expectBody(String.class).isEqualTo("Harry Potter"); } }
Testing in Spring Boot with MockMvc
As we have seen in some previous examples, the use of MockMvc allows us to make HTTP requests to our API or controller layer.
MockMvc allows us to make HTTP requests with different verbs and add both headers and parameters as needed. To use MockMvc, it is important to add its autoconfiguration through the @AutoConfigureMockMvc annotation.
@AutoConfigureMockMvc @SpringBootTest public class BookControllerIT { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @Test void findById() throws Exception { var book = createBook(); mockMvc.perform( MockMvcRequestBuilders.post("/api/books") .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(book))) .andExpect(status().isCreated()); var findById = mockMvc.perform( get("/api/books/1").accept(MimeTypeUtils.APPLICATION_JSON_VALUE)) .andExpect(status().isOk()) .andReturn(); var b = objectMapper.readValue(findById.getResponse().getContentAsString(), Book.class); assert b.getIsbn().equalsIgnoreCase("1A2s-3f"); } private Book createBook() { Book book = new Book(); book.setIsbn("1A2s-3f"); book.setTitle("The Count of Monte Cristo"); book.setPrice(34); return book; } }
Integration test with TestRestTemplate
When we want to perform integrated tests of our application, usually to perform integrated tests by invoking our API through HTTP requests, it is very common to use MockMvc. Although Spring provides us with an HTTP client to make these requests, just as we use RestTemplate, we can use TestRestTemplate for our testing part.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class StudentControllerTests { @LocalServerPort private int port; TestRestTemplate restTemplate = new TestRestTemplate(); HttpHeaders headers = new HttpHeaders(); @Test public void testCreateBook() throws Exception { HttpEntity<String> entity = new HttpEntity<String>(null, headers); ResponseEntity<String> response = restTemplate.exchange( createURLWithPort("/books"), HttpMethod.POST, entity, String.class); String actual = response.getHeaders().get(HttpHeaders.LOCATION).get(0); assertTrue(actual.contains("/books")); } }
As we can see in the previous example, we have used TestRestTemplate to make a POST request.
Unit tests of our controller with @WebMvcTest
We are going to use @WebMvcTest to perform a unit test of our Controller layer. This layer is the entry point to our application and is responsible for receiving HTTP requests.
Spring provides us with the @WebMvcTest annotation to facilitate unit tests in our controllers.
@RequestMapping("/api") @RequiredArgsConstructor @RestController public class BookController { private final BookService bookService; @GetMapping("/books/{id}") public ResponseEntity<Book> getBookById(@PathVariable Long id) { return ResponseEntity.ok(bookService.findById(id)); } @PostMapping("/books") public ResponseEntity<Book> saveBook(@RequestBody Book book) { var save = bookService.saveBook(book); return new ResponseEntity<>(HttpStatus.CREATED); } }
Our controller will call the service layer where the logic resides, so we are going to mock that layer:
@WebMvcTest(BookController.class) public class BookControlerTest { @Autowired private MockMvc mockMvc; @MockBean private BookService service; @Autowired private ObjectMapper objectMapper; @Test public void givenBooks_whenGetBookById_thenReturnBook() throws Exception { Book book = createBook(); given(service.findById(1L)).willReturn(book); var findById = mockMvc.perform( get("/api/books/1") .accept(MimeTypeUtils.APPLICATION_JSON_VALUE)) .andExpect(status().isOk()) .andReturn(); var b = objectMapper.readValue(findById.getResponse().getContentAsString(), Book.class); assert b.getIsbn().equalsIgnoreCase("1A2s-3f"); } private Book createBook() { Book book = new Book(); book.setIsbn("1A2s-3f"); book.setTitle("Numancia"); book.setPrice(34); return book; } }
By using the @WebMvcTest annotation, MockMvc will be auto-configured so that we can make HTTP requests to our controller.
Testing in Spring Boot with @MockBean
Usually, our applications or microservices will be composed of several layers. For example, our Controller layer of the previous example depends on the Service layer. Or our Service layer depends on our Repository. So for those cases where we need to perform unit tests of the different layers of our application, we are going to use @MockBean.
@MockBean will allow us to mock entire layers of our application, for our next example, we are going to mock the repository layer of our application, since we are only interested in the Service layer:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class BookServiceTest { @Autowired private BookService bookService; @MockBean private BookRepository bookRepository; @Test public void testRetrieveBookWithMockRepository() throws Exception { Optional<Book> optStudent = Optional.of(createBook()); when(bookRepository.findById(1L)).thenReturn(optStudent); assert bookService.findById(1L).getTitle().contains("Numancia"); } private Book createBook() { Book book = new Book(); book.setIsbn("1A2s-3f"); book.setTitle("Numancia"); book.setPrice(34); return book; } }
Conclusion
In this post about Examples of Testing in Spring Boot, we have seen the different ways that Spring offers us to perform testing tasks, both in unit tests and in integrated tests.
@SpringBootTest will facilitate almost all of our testing tasks by starting an Application Context, which will be very similar to the one we will have when our application is started. On the other hand, if we do not need to start an application context, we can avoid using @SpringBootTest and use only @MockBean, or even if we only want to test against our repository layer, we can use @DataJpaTest.
With all the possibilities of parameterization that Spring Boot offers us, there is no excuse for not performing testing tasks.
If you want to see different examples, you can take a look at our refactorizando GitHub repository here.
If you need more information, you can leave us a comment or send an email to refactorizando.web@gmail.com You can also contact us through our social media channels on Facebook or twitter and we will be happy to assist you!!