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);
}
}