AdBlock Detected

It looks like you're using an ad-blocker!

Our team work realy hard to produce quality content on this website and we noticed you have ad-blocking enabled. Advertisements and advertising enable us to continue working and provide high-quality content.

Testing with Spring Boot

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.

testing with spring boot | Examples of Testing in Spring Boot
testing with spring boot

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.

Until version 2.1 of Spring Boot, @ExtendWith was added to tell JUnit 5 to enable Spring support. Since Spring Boot 2.1, this annotation is no longer needed because it is included as a meta-annotation in Spring Boot test annotations such as @DataJpaTest, @WebMvcTest, and @SpringBootTest.

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!!

Leave a Reply

Your email address will not be published. Required fields are marked *