javarestjunitmockitospring-boot-test

why am i getting this error java.lang.AssertionError: JSON path "$" Expected: a collection with size <3> but: collection size was <0>


I'm trying for first time to write a test for my api and getting this error java.lang.AssertionError: JSON path "$" Expected: a collection with size <3> but: collection size was <0> . I have tested my api with openApi and it worked fine. here is my code

UserControllerTest.java

package com.RCTR.usersys;
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 static org.hamcrest.Matchers.*;
import org.aspectj.lang.annotation.Before;
import java.util.Arrays;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MockMvcBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import com.RCTR.usersys.controller.UserController;
import com.RCTR.usersys.entity.UserEntity;
import com.RCTR.usersys.model.User;
import com.RCTR.usersys.repository.UserRepository;
import com.RCTR.usersys.service.UserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;

@ExtendWith(MockitoExtension.class)
public class UserControllerTest {
    private MockMvc mockMvc;
    ObjectMapper objectMapper = new ObjectMapper();
    ObjectWriter objectwriter = objectMapper.writer();
    @Mock
    private UserService userService;
    @InjectMocks
    private UserController userController;
    
    User USER_1 = new User(1L ,"Laur","Pinzaru","laur@pinz.com" );
    User USER_2 = new User(2L ,"Petru","Gologan","petru@gologan.com" );
    User USER_3 = new User(3L ,"Luca","Pinzaru","luca@pinz.com" );

    @BeforeEach
    public void setUp(){
        MockitoAnnotations.openMocks(this);
        this.mockMvc = MockMvcBuilders.standaloneSetup(userController).build();
    }

    @Test
    public void getAllUsersSuccess() throws Exception{
        List<User> users = new ArrayList<>(Arrays.asList(USER_1,USER_2,USER_3));
        Mockito.when(userService.getAllUsers()).thenReturn(users);
        System.out.println("Mocked users: " + userService.getAllUsers());

        mockMvc.perform(MockMvcRequestBuilders
                .get("/api/v1/users")
                .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
               
                 .andExpect(MockMvcResultMatchers.jsonPath("$", hasSize(3)))
                 .andExpect(jsonPath("$[2].firstName", is("Luca")));
                // .andExpect(jsonPath("$[1].lastName", is(Gologan)));
                
        
        
    }
    
}   

and this is my controller

package com.RCTR.usersys.controller;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.RCTR.usersys.model.User;
import com.RCTR.usersys.service.UserService;

@RestController
@RequestMapping("/api/v1/")
public class UserController {
    
private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

@PostMapping("/user")
public User saveUser(@RequestBody User user){
    return userService.saveUser(user);
}

@GetMapping("/users")
public List<User> getAllUsers(){
    List<User> users = userService.getAllUsers();
    System.out.println("Users from service: " + users);
    return users;
       // return userService.getAllUsers();
}

@GetMapping("/users/{id}")
public ResponseEntity<User> getUserById(@PathVariable("id")Long id){
    User user = null;
    user = userService.getUserById(id);
    return ResponseEntity.ok(user);
}

@DeleteMapping("/users/{id}")
public ResponseEntity<Map<String , Boolean>> deleteUser(@PathVariable("id")Long id){
boolean deleted = false;
deleted = userService.deleteUser(id);
Map<String,Boolean> response = new HashMap<>();
 if (deleted) {
        response.put("deleted", true);
        return ResponseEntity.ok(response);
    } else {
        response.put("deleted", false);
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response);
    }
}

@PutMapping("/users/{id}")
public ResponseEntity<User> updateUser(@PathVariable("id")Long id,@RequestBody User user){
user = userService.updateUser(id,user);
    return ResponseEntity.ok(user);
}

}

Thank you in advance

i tried to use directly UserRepository , adn i got different error , then i tried to add logs for debugging with system.out.print but nothing appeared in test result window(i'm using vscode)

P.S i'm new to Spring and this type of programming )) and excuse for my bad english


Solution

  • I have gone ahead and slimmed down the test a bit but this should work and capture the original intent.

    @ExtendWith(SpringExtension.class)
    @AutoConfigureMockMvc
    @SpringBootTest
    class UserControllerTest {
    
        @Autowired
        private MockMvc mockMvc;
    
        @MockitoBean
        private UserService userService;
    
        private final List<User> users = List.of(
            new User(1L ,"Laur","Pinzaru","laur@pinz.com" ),
            new User(2L ,"Petru","Gologan","petru@gologan.com" ),
            new User(3L ,"Luca","Pinzaru","luca@pinz.com" )
        );
        
    
        @Test
        void getAllUsersSuccess() throws Exception{
    
            Mockito.when(userService.getAllUsers()).thenReturn(users);
    
            mockMvc.perform(MockMvcRequestBuilders
                    .get("/api/v1/users")
                    .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(3)))
                .andExpect(jsonPath("$[2].firstName").value("Luca"));
    
        }
    
    }
    

    Originally we were creating an instance of the controller but not testing against that instance meaning the mock was never called meaning that no results were returned.

    This version simplifies the setup by using the Spring extension and auto configuration of MockMvc.

    Alternatively to avoid the need to have things like the repository correctly configured you could opt for a setup like this:

    @AutoConfigureMockMvc
    @WebMvcTest(UserController.class)
    class UserControllerTest {
    
        @Autowired
        private MockMvc mockMvc;
    
        @MockitoBean
        private UserService userService;
    
        private final List<User> users = List.of(
            new User(1L ,"Laur","Pinzaru","laur@pinz.com" ),
            new User(2L ,"Petru","Gologan","petru@gologan.com" ),
            new User(3L ,"Luca","Pinzaru","luca@pinz.com" )
        );
    
        @Test
        void getAllUsersSuccess() throws Exception{
    
            Mockito.when(userService.getAllUsers()).thenReturn(users);
    
            mockMvc.perform(MockMvcRequestBuilders
                    .get("/api/v1/users")
                    .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(3)))
                .andExpect(jsonPath("$[2].firstName").value("Luca"));
        }
    }
    

    This approach removes the need for spring boot test and the spring extension which simplifies things further.

    Hope this helps!