October 23, 2020

In this 3rd and final article of the Spring Boot Testing series, we are going to discuss how to test our REST APIs developed using Spring’s MockMvc

We are going to take the Reddit Clone Application we built in this tutorial as an example project for this tutorial, you can check out the source code of this tutorial here

You can download the source code of this tutorial from Github

If you are a visual learner, like me , you can check out the video version of this tutorial below:

If we have a look at the architecture diagram of the Reddit Clone Application again we can see that it is divided into 3 layers:

Spring Boot Testing - Architecture Diagram of Reddit Clone Application

We are going to mainly concentrate on the tests to be written for the Controller Layer.

As the Controller Layer, is communicating with Service Layer we have to mock out the interaction with this layer to effectively able to test it in isolation.

For that we are going to make use of Mockito again in our tests.

Testing Web Layer with the help of @WebMvcTest

The Spring Test Framework provides us with an annotation called @WebMvcTest which is a specialized annotation which will create the Spring Context for us with only beans which are related to the Spring MVC components like @Controller, @RestController, @AutoconfigureWebMvc etc.

This is similar to @DataJpaTest annotation we saw in Part 2 of the Spring Boot Testing Tutorial series

Let’s go ahead and write our test for the PostController.java class

PostController.java

package com.programming.techie.springredditclone.controller;

import com.programming.techie.springredditclone.dto.PostRequest;
import com.programming.techie.springredditclone.dto.PostResponse;
import com.programming.techie.springredditclone.service.PostService;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

import static org.springframework.http.ResponseEntity.status;

@RestController
@RequestMapping("/api/posts/")
@AllArgsConstructor
public class PostController {

    private final PostService postService;

    @PostMapping
    public ResponseEntity<Void> createPost(@RequestBody PostRequest postRequest) {
        postService.save(postRequest);
        return new ResponseEntity<>(HttpStatus.CREATED);
    }

    @GetMapping
    public ResponseEntity<List<PostResponse>> getAllPosts() {
        return status(HttpStatus.OK).body(postService.getAllPosts());
    }

    @GetMapping("/{id}")
    public ResponseEntity<PostResponse> getPost(@PathVariable Long id) {
        return status(HttpStatus.OK).body(postService.getPost(id));
    }

    @GetMapping("by-subreddit/{id}")
    public ResponseEntity<List<PostResponse>> getPostsBySubreddit(Long id) {
        return status(HttpStatus.OK).body(postService.getPostsBySubreddit(id));
    }

    @GetMapping("by-user/{name}")
    public ResponseEntity<List<PostResponse>> getPostsByUsername(String username) {
        return status(HttpStatus.OK).body(postService.getPostsByUsername(username));
    }
}

PostControllerTest.java

package com.programming.techie.springredditclone.controller;

import com.programming.techie.springredditclone.dto.PostResponse;
import com.programming.techie.springredditclone.security.JwtProvider;
import com.programming.techie.springredditclone.service.PostService;
import com.programming.techie.springredditclone.service.UserDetailsServiceImpl;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static java.util.Arrays.asList;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(controllers = PostController.class)
public class PostControllerTest {

    @MockBean
    private PostService postService;
    @MockBean
    private UserDetailsServiceImpl userDetailsService;
    @MockBean
    private JwtProvider jwtProvider;

    @Autowired
    private MockMvc mockMvc;

    @Test
    @DisplayName("Should List All Posts When making GET request to endpoint - /api/posts/")
    public void shouldCreatePost() throws Exception {
        PostResponse postRequest1 = new PostResponse(1L, "Post Name", "http://url.site", "Description", "User 1",
                "Subreddit Name", 0, 0, "1 day ago", false, false);
        PostResponse postRequest2 = new PostResponse(2L, "Post Name 2", "http://url2.site2", "Description2", "User 2",
                "Subreddit Name 2", 0, 0, "2 days ago", false, false);

        Mockito.when(postService.getAllPosts()).thenReturn(asList(postRequest1, postRequest2));

        mockMvc.perform(get("/api/posts/"))
                .andExpect(status().is(200))
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
                .andExpect(jsonPath("$.size()", Matchers.is(2)))
                .andExpect(jsonPath("$[0].id", Matchers.is(1)))
                .andExpect(jsonPath("$[0].postName", Matchers.is("Post Name")))
                .andExpect(jsonPath("$[0].url", Matchers.is("http://url.site")))
                .andExpect(jsonPath("$[1].url", Matchers.is("http://url2.site2")))
                .andExpect(jsonPath("$[1].postName", Matchers.is("Post Name 2")))
                .andExpect(jsonPath("$[1].id", Matchers.is(2)));
    }
}

Let’s dissect the above test and see what is happening:

  • We annotated our Test class using @WebMvcTest annotation, which will spin up the Spring Context with only classes related to the Spring MVC components.
  • As we are only testing the PostController we can mock out the interaction to the PostService using the @MockBean annotation, by adding this annotation Spring will inject the mocks of the required dependencies.
  • To bootstrap the PostService we also need access to UserDetailsImpl and JwtProvider classes, so we mocked out also these dependencies using @MockBean
  • Next, we are auto wiring the MockMvc class which we use mainly to interact with the REST APIs
  • Inside our test, we are writing a test for the GET endpoint /api/posts/, if you check the logic for this endpoint, it’s calling the getAllPosts() method of the PostService class.
  • We are mocking out the call to this method and returning a List<PostResponse> as response.
  • To make the REST API call we are using the mockMvc.perform() method , and with the return type we are making assertion that the required HTTP STATUS is 200, followed by the assertions for the data which is returned from the endpoint.

If you run the above test, you should see the tests should pass without any problems.

Conclusion

This concludes our Spring Boot Testing Tutorial series, I hope you learned some about how to write Tests for the Web Layer using MockMvc.

Stay tuned for more articles.

About the author 

Sai Upadhyayula

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

Subscribe now to get the latest updates!