Initializations:
@Autowired
private MockMvc mockMvc;
@MockBean
private ProductServiceImp productServiceImp;
@Autowired
private ObjectMapper objectMapper;
Test method for updateProduct Controller:
@Test
@Order(3)
public void updateProductController_ShouldReturnOk() throws Exception {
// Arrange
when(productServiceImp.updateProduct(product, 1L)).thenReturn(product);
// Act
ResultActions response = mockMvc.perform(put("/api/products/{id}", 1L)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(product))
);
// Assert
response.andExpectAll(
status().isOk(),
jsonPath("$.title", CoreMatchers.is(product.getTitle()))
);
}
Controller class:
//Note: I have @RequestMapping(value = "api/products") at the top of the class
@PutMapping("/{id}")
public ResponseEntity<Product> updateProduct(@PathVariable Long id, @RequestBody Product product) {
Product updatedProduct = productService.updateProduct(product, id);
return ResponseEntity.ok(updatedProduct);
}
The status code 200 is fine but it seems there is no value at JSON path "$.title".
Here is the request:
MockHttpServletRequest:
HTTP Method = PUT
Request URI = /api/products/1
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"246"]
Body = {"id":1,"title":"Fake Product","price":99999999,"rating":{"rate":5.0,"count":1},"category":"Fake Category","description":"Fake description","image":"FakeImage.jpg"}
Session Attrs = {}
Here is the response:
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
I tried to return body explicitly from the Controller class but that did not work either. As, I am expecting 200 status and Json data to cross check the field (title) but I get java.lang.AssertionError: No value at JSON path "$.title" error.
The equality check for the Product test instance registered for the mock service and that in the controller fails.
This is why the mock behavior doesn't match the registered arguments and returns null when the updateProduct method of the mock service implementation is called.
Product instance deserialized from the request body in the controller and passed down to the service is different from that registered for the matching arguments for the method of your mock service.
You can register the mock behaviour to match any instance of the Product class this way:
when(
productServiceImp.updateProduct(
eq(1L), any(Product.class))
.thenReturn(product);
If you would like to keep a strict equality match on the arguments to the updateProduct method of your service,
you can customise how equality is performed in your Product class by overriding its equals and hashCode methods.
// Assuming that id attribute distinctively identifies the `Product`.
public class Product {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Product product = (Product) o;
return Objects.equals(id, product.id)
&& Objects.equals(name, product.title)
&& Objects.equals(price, product.price);
}
@Override
public int hashCode() {
return Objects.hash(id, title, price);
}
}