Search code examples
javaspring-bootjunitspring-restcontroller

perfoming a Junit test with JsonPath not working


I am running a unit test on a rest controller method addUser() which executes successfully without JSON path. Anytime I try to use jsonPath to perform strict data assertion to verify the content of the JSON document, I get the error No value at JSON path "$.first_name". Any help on how to fix the issue will be much appreciated. Below is the code sample

@Entity
@Table(name = "users")
public class User extends IdBaseEntity{

    @JsonProperty("first_name")
    @Column(name = "first_name", nullable = false, length = 45)
    @NotBlank(message = "First name cannot be blank")
    @Length(min = 2, max = 45)
    private String firstName;
    
    @JsonProperty("last_name")
    @NotBlank(message = "Last name cannot be blank")
    @Length(min = 2, max = 45)
    @Column(name="last_name", nullable = false, length = 45)
    private String lastName;
    
    
    @NotBlank(message = "Email cannot be blank")
    @Length(min = 2, max = 45)
    @Column(nullable = false, unique = true, length = 45)
    private String email;
    

    @NotBlank(message = "Password name cannot be blank")
    @Length(min = 2, max = 45)
    @Column(nullable = false, length = 45)
    private String password;
    
    @JsonProperty("phone_number")
    @Column(length = 13)
    private String phoneNumber;
    
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
            name = "users_roles",
            joinColumns = @JoinColumn(name ="user_id"),
            inverseJoinColumns = @JoinColumn(name="role_id")
            )
    private Set<Role> roles = new HashSet<>();
    
    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }


    @Override
    public String toString() {
        return "User [firstName=" + firstName + ", lastName=" + lastName + ", email=" + email + ", password=" + password
                + ", phoneNumber=" + phoneNumber + "]";
    }
    
}

service interface

public interface UserService {
    
    public User addUser(User user); 
    
}

service class

@Service
public class UserServiceImpl implements UserService{
    
    private UserRepository userRepo;
    
    @Autowired
    public UserServiceImpl(UserRepository userRepo) {
        this.userRepo = userRepo;
    }

    @Override
    public User addUser(User user) {
        return userRepo.save(user);
    }
    
}

restcontroller class

@RestController
@RequestMapping("/api/v1/users")
public class UserRestApiController {

    private UserServiceImpl userService;
    
    public UserRestApiController(UserServiceImpl userService) {
        this.userService = userService;
    }
    
    @PostMapping
    public ResponseEntity<User> addUser(@RequestBody @Valid User user){
        User newUser = userService.addUser(user);
        
        URI uri = URI.create("/api/v1/users");
        
        return ResponseEntity.created(uri).body(newUser);
    }
    
}

test class

@WebMvcTest(UserRestApiController.class)
public class UserRestApiControllerTest {
    
    //end point uri
    private static final String END_POINT_PATH = "/api/v1/users";
    
    @MockBean 
    private UserServiceImpl userService;
    
    @Autowired MockMvc mockMvc;
    
    @Autowired ObjectMapper objectMapper;
    
    @Test
    public void testAddUserShouldReturn201Created() throws Exception {
    
        User user = new User();
        user.setFirstName("James");
        user.setLastName("Kwarteng");
        user.setEmail("james@gmail.com");
        user.setPassword("23464433");
        user.setPhoneNumber("04233455");
        
        //fakes the UserServiceImpl class
        Mockito.when(userService.addUser(user)).thenReturn(user);
        
        //convert user object to json object 
        String bodyContent = objectMapper.writeValueAsString(user);
        
        //perform http request
        mockMvc.perform(post(END_POINT_PATH).contentType("application/json").content(bodyContent))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.first_name", is("James")))
            .andDo(print());
        
    }
}

error after running the test. part of the error code is omitted

java.lang.AssertionError: No value at JSON path "$.first_name"
    at org.springframework.test.util.JsonPathExpectationsHelper.evaluateJsonPath(JsonPathExpectationsHelper.java:302)
    at org.springframework.test.util.JsonPathExpectationsHelper.assertExistsAndReturn(JsonPathExpectationsHelper.java:326)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:452)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
Caused by: java.lang.IllegalArgumentException: json can not be null or empty
    at com.jayway.jsonpath.internal.Utils.notEmpty(Utils.java:401)

Solution

  • json can not be null or empty. It doesn't work with jsonPath because the response body is always empty but the status is 201.

    The Mockito.when...thenReturn is not propagated in the controller class.

    MockHttpServletResponse:
               Status = 201
        Error message = null
              Headers = [Location:"/api/v1/users"]
         Content type = null
                 Body = 
        Forwarded URL = null
       Redirected URL = /api/v1/users
              Cookies = []
    

    You need to involve Mockito by adding @ExtendWith(MockitoExtension.class) instead of @WebMvcTest(UserRestApiController.class). The second step is to build a MockMvc instance by registering the UserRestApiController @Controller instance.

    Below is the fixed code that works.

    @ExtendWith(MockitoExtension.class)
    class UserRestApiControllerTest {
    
        private static final String END_POINT_PATH = "/api/v1/users";
    
        @InjectMocks
        private UserRestApiController userRestApiController;
    
        @Mock
        private UserServiceImpl userService;
    
        private MockMvc mockMvc;
    
        @BeforeEach
        public void setUp() {
            mockMvc = MockMvcBuilders.standaloneSetup(userRestApiController).build();
        }
    
        @Test
        void testAddUserShouldReturn201Created() throws Exception {
    
            User user = new User();
            user.setFirstName("James");
            user.setLastName("Kwarteng");
            user.setEmail("james@gmail.com");
            user.setPassword("23464433");
            user.setPhoneNumber("04233455");
    
            //fakes the UserServiceImpl class
            Mockito.when(userService.addUser(any(User.class))).thenReturn(user);
    
            //convert user object to json object
            String bodyContent = new ObjectMapper().writeValueAsString(user);
    
            //perform http request
            mockMvc.perform(MockMvcRequestBuilders.post(END_POINT_PATH).contentType("application/json").content(bodyContent))
                    .andExpect(status().isCreated())
                    .andExpect(jsonPath("$.first_name").value("James"))
                    .andExpect(jsonPath("$.last_name").value("Kwarteng"))
                    .andExpect(jsonPath("$.email").value("james@gmail.com"))
                    .andExpect(jsonPath("$.password").value("23464433"))
                    .andExpect(jsonPath("$.phone_number").value("04233455"))
                    .andDo(print());
        }
    }
    

    the MockHttpServletResponse:

    MockHttpServletResponse:
               Status = 201
        Error message = null
              Headers = [Location:"/api/v1/users", Content-Type:"application/json"]
         Content type = application/json
                 Body = {"email":"james@gmail.com","password":"23464433","first_name":"James","last_name":"Kwarteng","phone_number":"04233455"}
        Forwarded URL = null
       Redirected URL = /api/v1/users
              Cookies = []