springjunitspring-testjsonpathspring-test-mvc

AssertionError: No value at JSON path "$.title" in Junit Mockito Test


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.


Solution

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