the/experts. Blog

Cover image for Spring Boot - How do you test your controllers?
Mitchel Wijt
Mitchel Wijt

Posted on

Spring Boot - How do you test your controllers?

The different testing methods

There are multiple testing methods out there. But in this post I would like to talk about the 2 most commonly used testing methods, which are:

  1. Integration testing
  2. Unit testing

1) Integration testing
Integration testing means we test a part of a system and its modules together as a group. We do not isolate the different modules that belong in a certain flow. Instead, we make sure to test all of them and check if we get the desired outcome.

For instance, looking at this common flow:
HTTP request -> Controller -> Service -> Repository -> Data
we would test the system from the front to the back including every layer along the flow.

2) Unit testing
Unit testing means that we test every module of a particular system flow in isolation as a "unit", hence the name Unit Testing.

Considering the same flow mentioned above in the Integration test section, namely HTTP request -> Controller -> Service -> Repository -> Data
We would write separate tests for each unit:

  • the controller
  • the service
  • the repository.

In the following, I will explain how this is achievable in the Spring Boot framework and what modules have to be used.

Integration testing a controller

If we want to start integration testing our controller we have a couple of annotations we need to keep in mind, both for integration tests in specific and for testing inside Spring Boot in general.

These are the annotations:

  • @SpringBootTest
  • @Test

Whenever we create a new test class for our controller, we annotate this class with @SpringBootTest.
this loads the complete Spring application context, meaning we do not have to mock any modules(units) within our tests. In case of integration testing that is precisely what we want to achieve.

Here is an example:

@SpringBootTest
@AutoConfigureMockMvc
class BlogControllerTest {
    @Autowired
    private MockMvc mockMvc;
}
Enter fullscreen mode Exit fullscreen mode

Every test method in the test class gets annotated with the @Test annotation. This tells the JUnit testing framework that this is an executable test method.

    @Test
    public void shouldCreateBlogPost() {}
Enter fullscreen mode Exit fullscreen mode

The only mock that we do need to instantiate for our controller test, is the HTTP request mock to the url our controller is configured for.

Here is an example of how to mock an HTTP request in a controller test.

 var response = this.mockMvc.perform(post("/api/blogs/create")
          .content(om.writeValueAsString(blog))
          .contentType(MediaType.APPLICATION_JSON)
 );
Enter fullscreen mode Exit fullscreen mode

Having done all that, now it's time to actually write the test assertions. Inside our test method shouldCreateBlogPost we can call our controller and test if the status and data it returns is what we expect it to be.

 response.andDo(print())
      .andExpect(status().isOk())           
      .andExpect(content().json(om.writeValueAsString(blog)));
Enter fullscreen mode Exit fullscreen mode

In summary, we need to keep in mind that when integration testing a controller we test the system flow end-to-end without mocking or isolating components.

Unit testing the controller

In Unit testing, as opposed to integration testing, we do want to isolate our components and test them separately.

With these types of tests, we mock everything that does not belong to the unit itself. For example, if a controller uses a service, we do not care about the internal workings of that service. We only care about the workings of the controller itself. Hence we mock any results the service should return.

Spring Boot has a different annotation to start a Unit Test. which is @WebMvcTest.

@WebMvcTest is only going to scan the controller you've defined and the MVC infrastructure. So if your controller has some dependency on other beans from your service layer, the test won't start until you either load that config yourself or provide a mock for it. This is much faster as we only load a tiny portion of our application.

Here is an example how to apply this annotation:

@WebMvcTest(BlogController.class)
class BlogControllerTest {
    ....
}
Enter fullscreen mode Exit fullscreen mode

Let's assume we want to test the method BlogController.createBlog(). First, we need to create a MockBean for the BlogService.

@MockBean
private BlogService blogService;
Enter fullscreen mode Exit fullscreen mode

Next we need to mock the method call.

BlogDto payload = new BlogDto("this is a title", "this is a desc");
Blog expected = new Blog("this is a title", "this is a desc");

when(blogService.createBlog(payload).thenReturn(expected);
Enter fullscreen mode Exit fullscreen mode

What we essentially say here is. That when the method BlogService.createBlog is called with the given payload, we return our static blog instance.

Next we need to assert if the expected blog is returned from our createBlog controller method.

public void shouldCreateBlog() {
BlogDto payload = new BlogDto("this is a title", "this is a desc");
Blog expected = new Blog("this is a title", "this is a desc");
        when(blogService.createBlog(payload).thenReturn(expected);

Blog resultBlog = blogController.createBlog(expected);
        assertThat(resultBlog.toString()).isEqualTo(expected.toString());
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

The main difference between Integration tests and Unit tests is the level of isolation of different components and therefore tightly connected to the degree of mocking within the test.

As we've shown in the examples, Spring Boot offers numerous annotations to make both testing methods really approachable.

If you have any additional questions or feedback, feel free to let me know!

Discussion (0)