jbailie1991 jbailie1991 - 24 days ago 7
reST (reStructuredText) Question

Spring Boot: MockMvc returning strange responses

I have the following controller:

RestApiController.java

package com.spring.ocr.rest.restconsume.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;

import com.spring.ocr.rest.restconsume.model.User;
import com.spring.ocr.rest.restconsume.service.UserService;
import com.spring.ocr.rest.restconsume.util.CustomErrorType;

@RestController
@RequestMapping("/api")
public class RestApiController {

@Autowired
UserService userService;

@RequestMapping(method = RequestMethod.GET, value = "/user/")
public ResponseEntity<List<User>> listAllUsers() {
final List<User> users = userService.findAllUsers();
if(users.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<List<User>>(users, HttpStatus.OK);
}

@RequestMapping(method = RequestMethod.GET, value = "/user/{id}")
public ResponseEntity<?> getUser(@PathVariable("id") long id) {
final User user = userService.findUserById(id);
if(user == null) {
return new ResponseEntity<>(new CustomErrorType(String.format("User with id %s not found", id)), HttpStatus.NOT_FOUND);
}
return new ResponseEntity<User>(user, HttpStatus.OK);
}

@RequestMapping(method = RequestMethod.POST, value = "/user/")
public ResponseEntity<?> createUser(@RequestBody User user, UriComponentsBuilder ucBuilder) {
if(userService.doesUserExist(user)) {
return new ResponseEntity<>(new CustomErrorType(String.format("Unable to create, user %s already exists", user.getName())), HttpStatus.CONFLICT);
}
userService.createUser(user);

final HttpHeaders headers = new HttpHeaders();
headers.setLocation(ucBuilder.path("/api/user/{id}").buildAndExpand(user.getId()).toUri());
return new ResponseEntity<String>(headers, HttpStatus.CREATED);
}

@RequestMapping(method = RequestMethod.PUT, value = "/user/{id}")
public ResponseEntity<?> updateUser(@PathVariable("id") long id,@RequestBody User user) {
User currentUser = userService.findUserById(id);
if(currentUser == null) {
return new ResponseEntity<>(new CustomErrorType(String.format("Unable to create update, User with id %s not found", user.getId())), HttpStatus.NOT_FOUND);
}
currentUser.setName(user.getName());
currentUser.setAge(user.getAge());
currentUser.setSalary(user.getSalary());

userService.updateUser(currentUser);
return new ResponseEntity<User>(currentUser, HttpStatus.OK);
}

@RequestMapping(method = RequestMethod.DELETE, value = "/user/{id}")
public ResponseEntity<?> deleteUser(@PathVariable("id") long id) {
User user = userService.findUserById(id);

if(user == null) {
return new ResponseEntity<>(new CustomErrorType(String.format("Unable to delete user, user with id %s not found", id)), HttpStatus.NOT_FOUND);
}
userService.deleteUserById(id);
return new ResponseEntity<User>(HttpStatus.NO_CONTENT);
}

@RequestMapping(method = RequestMethod.DELETE, value = "/user/")
public ResponseEntity<User> deleteAllUsers() {
userService.deleteAllUsers();
return new ResponseEntity<User>(HttpStatus.NO_CONTENT);
}
}


And I've set up a test of the web layer using mockMvc, with the user service bean mocked out as is standard:

RestApiControllerUnitTest.java

package com.spring.ocr.rest.restconsume.controller;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.util.ArrayList;
import java.util.List;

import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
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.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.spring.ocr.rest.restconsume.model.User;
import com.spring.ocr.rest.restconsume.service.UserService;

@RunWith(SpringRunner.class)
@WebMvcTest(RestApiController.class)
public class RestApiControllerUnitTest {

@Autowired
private MockMvc mockMvc;
@MockBean
UserService userService;
@Autowired
ObjectMapper objectMapper;

private final List<User> dummyUserList = getDummyUserList();
private final User dummyUser = new User((long)1, "Dave", (short)30, (double)30000);
private final User dummyUpdatedUser = new User((long)1, "David", (short)31, (double)35000);

@Test
public void test_listAllUsers_userListSizeIs4_returnsListSizeOf4AndOkStatus() throws Exception {
when(userService.findAllUsers()).thenReturn(dummyUserList);
this.mockMvc.perform(get("/api/user/"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(4)));
}

@Test
public void test_listAllUsers_userListIsEmpty_returnsNoContentStatus() throws Exception {
when(userService.findAllUsers()).thenReturn(new ArrayList<User>());
this.mockMvc.perform(get("/api/user/"))
.andDo(print())
.andExpect(status().isNoContent());
}

@Test
public void test_getUser_userExists_returnsUser() throws Exception {
when(userService.findUserById(dummyUser.getId())).thenReturn(dummyUser);
this.mockMvc.perform(get("/api/user/" + dummyUser.getId()))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.id",is((int)dummyUser.getId())))
.andExpect(jsonPath("$.name", is(dummyUser.getName())))
.andExpect(jsonPath("$.age", is((int)dummyUser.getAge())))
.andExpect(jsonPath("$.salary", is(dummyUser.getSalary())));
}

@Test
public void test_getUser_userDoesntExist_returnsNotFoundStatusAndCustomErrorString() throws Exception {
when(userService.findUserById(dummyUser.getId())).thenReturn(null);
this.mockMvc.perform(get("/api/user/"+dummyUser.getId()))
.andDo(print())
.andExpect(status().isNotFound())
.andExpect(content().string(containsString("User with id 1 not found")));
}

@Test
public void test_createUser_userDoesNotExist_userCreated() throws Exception {
final String dummyUserJson = objectMapper.writeValueAsString(dummyUser);
when(userService.doesUserExist(dummyUser)).thenReturn(false);
MvcResult result = this.mockMvc.perform(post("/api/user/")
.contentType(MediaType.APPLICATION_JSON)
.content(dummyUserJson))
.andDo(print())
.andExpect(status().isCreated())
.andReturn();
final String header = result.getResponse().getHeader("Location");
assertThat(header, is("http://localhost/api/user/1"));
}

@Test
public void test_createUser_userExists_returnsNotFoundStatusAndCustomErrorMessage() throws Exception {
final String dummyUserJson = objectMapper.writeValueAsString(dummyUser);
final String expectedContent = String.format("Unable to create, user %s already exists", dummyUser.getName());
when(userService.doesUserExist(anyObject())).thenReturn(true);
this.mockMvc.perform(post("/api/user/")
.contentType(MediaType.APPLICATION_JSON)
.content(dummyUserJson))
.andDo(print())
.andExpect(status().isConflict())
.andExpect(jsonPath("$.errorMessage", is(expectedContent)));
}

@Test
public void test_updateUser_userExists_returnsOkStatusAndUpdatedUser() throws Exception {
final String dummyUpdatedUserJson = objectMapper.writeValueAsString(dummyUpdatedUser);
when(userService.findUserById(dummyUser.getId())).thenReturn(dummyUser);
doNothing().when(userService).updateUser(dummyUser);
this.mockMvc.perform(put("api/user/" + dummyUser.getId())
.contentType(MediaType.APPLICATION_JSON)
.content(dummyUpdatedUserJson))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.age", is(dummyUpdatedUser.getAge())))
.andExpect(jsonPath("$.name", is(dummyUpdatedUser.getName())))
.andExpect(jsonPath("$.salary", is(dummyUpdatedUser.getSalary())));
}

@Test
public void test_deleteUser_userExists_returnNoContentStatus() throws Exception {
when(userService.findUserById(dummyUser.getId())).thenReturn(dummyUser);
this.mockMvc.perform(delete("api/user/" + dummyUser.getId()))
.andDo(print())
.andExpect(status().isNotFound());
}

@Test
public void test_deleteUser_userDoesntExist_returnsNotFoundStatusAndCustomErrorMessage () throws Exception {
when(userService.findUserById(dummyUser.getId())).thenReturn(null);
final String expectedContent = String.format("Unable to create update, User with id %s not found", dummyUser.getName());
this.mockMvc.perform(delete("api/user/"+dummyUser.getId()))
.andDo(print())
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.errorMessage", is(expectedContent)));
}

@Test
public void test_deleteAllUsers_usersListPopulated_returnNoContentStatus() throws Exception {
this.mockMvc.perform(delete("api/user/").contentType(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isNotFound());
}

private List<User> getDummyUserList() {
final List<User> dummyUserList = new ArrayList<>();
dummyUserList.add(new User((long)1, "Dave", (short)30, (double)30000));
dummyUserList.add(new User((long)2, "Jess", (short)20, (double)20000));
dummyUserList.add(new User((long)3, "Mike", (short)40, (double)40000));
dummyUserList.add(new User((long)4, "Molly", (short)50, (double)50000));
return dummyUserList;
}
}


The test
test_updateUser_userExists_returnsOkStatusAndUpdatedUser
is returning a 404 rather than a 200 status and
test_deleteUser_userDoesntExist_returnsNotFoundStatusAndCustomErrorMessage
is not returning the error message in the body, which alludes to the 404 not being a "genuine" 404(its not returned because the correct response is coming back, its returning it for some other reason). Im also thinking that some of the other 404 statuses may be returned under the same context.

Answer Source

Issue was due to missing "/" on the controller methods used in the failing tests.