SpringBoot Rest API controller unit-test with Mockito.

Nishanth Reddy Emmadi
3 min readJul 2, 2020

I am a software professional working with multiple projects, I started thinking to document my learnings so I can be useful to others, so I am here with my first Medium blog.

This post is about testing a REST API controller in a springBoot application.

I know TDD(Test Driven Development) is a development technique where you must first write a test that fails before you write new functional code. In this post my focus is not TDD but the end product and the intension is same.

Here I create an analogy representing a typical micro-service architecture having controller that calls the service, which indeed perform some logic and return some data back to controller. My goal here is to unit test the controller.

As an example I created a controller to handle an API call “api/v1/weather/{city}”. This API call is responsible for get weather based on city.

WeatherController.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "api/v1",headers = "Accept=application/json")
public class WeatherController {

@Autowired
private WeatherService weatherService;

@GetMapping(value = "/weather/{cityname}")
public ResponseEntity<WeatherResponse> getCurrentWeather(@PathVariable String cityname){

WeatherResponse response = weatherService.getCityWeather(cityname);

return response != null ? new ResponseEntity<>(response, HttpStatus.OK) : new ResponseEntity<>(HttpStatus.NO_CONTENT);

}

}

First thing comes to my mind when I need to need to write a unit test cases is Identifying the logic I need to test. It is not about the test coverage, It is about quality of the test itself.

Step 1: Identity the logic that needs to be test

a) First thing for a controller test is to test the URL itself, Test should make. sure the controller is reachable with the provided URL.

b) Next one is to test the service is being called with the same request that is being called with.

c) I need to test both success and failure scenario’s i.e, HTTP 200 success on found and HTTP 204 on Not found.

Step 2: Write the actual Test

package org.optimize.database.reads.controller;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;


@RunWith(MockitoJUnitRunner.class)
public class WeatherControllerTest {

private MockMvc mockMvc;

@InjectMocks
private WeatherController subject;

@Mock
private WeatherService weatherService;

@Captor
private ArgumentCaptor<String> stringArgumentCaptor;

@Mock
private WeatherResponse mockResponse;

@Before
public void setup(){

mockMvc = MockMvcBuilders.standaloneSetup(subject).build();
}

@Test
public void test_getCurrentWeather_success() throws Exception {

when(weatherService.getCityWeather(stringArgumentCaptor.capture())).thenReturn(mockResponse);

String url= "/api/v1/weather/{cityname}";

MvcResult mvcResult = this.mockMvc.perform(get(url,"OMAHA")).andExpect(status().isOk()).andReturn();

verify(weatherService,times(1)).getCityWeather(anyString());
assertEquals("OMAHA",stringArgumentCaptor.getValue());
assertEquals(HttpStatus.OK, mvcResult.getResponse().getStatus());
assertSame(mockResponse,mvcResult.getResponse());

}

@Test
public void test_getCurrentWeather_failure() throws Exception {

when(weatherService.getCityWeather(stringArgumentCaptor.capture())).thenReturn(null);

String url= "/api/v1/weather/{cityname}";

MvcResult mvcResult = this.mockMvc.perform(get(url,"ABC").contentType(MediaType.APPLICATION_JSON)).andExpect(status().isNoContent()).andReturn();

verify(weatherService,times(1)).getCityWeather(anyString());
assertEquals("ABC",stringArgumentCaptor.getValue());
assertEquals(HttpStatus.NO_CONTENT.value(), mvcResult.getResponse().getStatus());

}

Example shown here is for a http GET method call, Similarly we follow for the POST, PUT, DELETE operations. For post operation or any operation that needs a request body should pass the request body as a json string.

We can use this method to convert the request object into Json string in our test.

private String jsonString(Object obj) throws JsonProcessingException {

return new ObjectMapper().writeValueAsString(obj);
}

Sample snippet to perform mockMvc for POST Method:

RequestBody newObjectInstance = new RequestBody();
// setting fields for the RequestBody

MvcResult mvcResult = this.mockMvc.perform(MockMvcRequestBuilders.post(uri)
.content(jsonString(newObjectInstance))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
).andExpect(status().isNoContent()).andReturn();

Please share your thoughts and correct my mistakes if any. Thank you.

--

--