Im trying to test my Spring REST controller but my #Service is always trying to connect to DB.
Controller:
#RestController
#RequestMapping(value = "/api/v1/users")
public class UserController {
private UserService userService;
#Autowired
public UserController(UserService userService) {
this.userService = userService;
}
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.findAll();
if (users.isEmpty()) {
return new ResponseEntity<List<User>>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<List<User>>(users, HttpStatus.OK);
}
Test:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#WebAppConfiguration
public class UserControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext wac;
#Before
public void setup() {
this.mockMvc = webAppContextSetup(wac).build();
}
#Test
public void getAll_IfFound_ShouldReturnFoundUsers() throws Exception {
User first = new User();
first.setUserId(1);
first.setUsername("test");
first.setPassword("test");
first.setEmail("test#email.com");
first.setBirthday(LocalDate.parse("1996-04-30"));
User second = new User();
second.setUserId(2);
second.setUsername("test2");
second.setPassword("test2");
second.setEmail("test2#email.com");
second.setBirthday(LocalDate.parse("1996-04-30"));
UserService userServiceMock = Mockito.mock(UserService.class);
Mockito.when(userServiceMock.findAll()).thenReturn(Arrays.asList(first, second));
mockMvc.perform(get("/api/v1/users")).
andExpect(status().isOk()).
andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)).
andExpect(jsonPath("$", hasSize(2))).
andExpect(jsonPath("$[0].userId", is(1))).
andExpect(jsonPath("$[0].username", is("test"))).
andExpect(jsonPath("$[0].password", is("test"))).
andExpect(jsonPath("$[0].email", is("test#email.com"))).
andExpect(jsonPath("$[0].email", is(LocalDate.parse("1996-04-30")))).
andExpect(jsonPath("$[1].userId", is(2))).
andExpect(jsonPath("$[1].username", is("test2"))).
andExpect(jsonPath("$[1].password", is("test2"))).
andExpect(jsonPath("$[1].email", is("test2#email.com"))).
andExpect(jsonPath("$[1].email", is(LocalDate.parse("1996-04-30"))));
verify(userServiceMock, times(1)).findAll();
verifyNoMoreInteractions(userServiceMock);
}
}
My test always failure because instead getting first and second as return, it reads data from DB. If I turn off DB, it throws NestedServletException, nested: DataAccessResourceFailureException.
How can i test it properly? What am I doing wrong?
Mocking userService this way UserService userServiceMock = Mockito.mock(UserService.class); will not inject it into the controller. Remove this line and inject userService as follows
#MockBean UserService userServiceMock;
As #M.Deinum suggested you can remove manual creation of MockMvc and autowired it
#Autowired
private MockMvc mockMvc;
At the end your code should look like
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#WebAppConfiguration
public class UserControllerTest {
#MockBean
UserService userServiceMock;
#Autowired
private MockMvc mockMvc;
#Test
public void getAll_IfFound_ShouldReturnFoundUsers() throws Exception {
User first = new User();
first.setUserId(1);
first.setUsername("test");
first.setPassword("test");
first.setEmail("test#email.com");
first.setBirthday(LocalDate.parse("1996-04-30"));
User second = new User();
second.setUserId(2);
second.setUsername("test2");
second.setPassword("test2");
second.setEmail("test2#email.com");
second.setBirthday(LocalDate.parse("1996-04-30"));
Mockito.when(userServiceMock.findAll())
.thenReturn(Arrays.asList(first, second));
mockMvc.perform(get("/api/v1/users")).
andExpect(status().isOk()).
andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)).
andExpect(jsonPath("$", hasSize(2))).
andExpect(jsonPath("$[0].userId", is(1))).
andExpect(jsonPath("$[0].username", is("test"))).
andExpect(jsonPath("$[0].password", is("test"))).
andExpect(jsonPath("$[0].email", is("test#email.com"))).
andExpect(jsonPath("$[0].email", is(LocalDate.parse("1996-04-30")))).
andExpect(jsonPath("$[1].userId", is(2))).
andExpect(jsonPath("$[1].username", is("test2"))).
andExpect(jsonPath("$[1].password", is("test2"))).
andExpect(jsonPath("$[1].email", is("test2#email.com"))).
andExpect(jsonPath("$[1].email", is(LocalDate.parse("1996-04-30"))));
verify(userServiceMock, times(1)).findAll();
verifyNoMoreInteractions(userServiceMock);
}
}
Related
I am trying to make testings for my user controller but when I run the test it fails because the response body is empty.
Note that the response code is 200 so i dont see where this problem comes from...
#WebMvcTest(controllers = {UserRestController.class})
public class UserControllerTest {
#Autowired
private WebApplicationContext webApplicationContext;
#MockBean
private UserService service;
private MockMvc mockMvc;
private User user1;
private User user2;
#BeforeEach
public void setUp() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
this.user1 = User.builder()
.email("doe.jean#mymel.com")
.username("Djin")
.build();
this.user2 = User.builder()
.email("doe.john#mymel.com")
.username("Jodo")
.build();
}
#Test
public void test_shouldReturnAllUsers() throws Exception {
Mockito.when(service.getUsers(Pageable.unpaged())).thenReturn(new PageImpl<>(Arrays.asList(user1, user2)));
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get(APIPaths.V1_USER_BASE + "/all")
.accept(MediaType.APPLICATION_JSON))
.andExpect(request().asyncStarted())
.andReturn();
mockMvc.perform(asyncDispatch(mvcResult))
.andExpect(status().is(200))
.andExpect(jsonPath("$.size()", Matchers.is(2)))
.andDo(print());
}
}
Here is my code. I can't see why it is not working. The problem is with the line in test :
when(applicationUserRepository.findApplicationUserByUsername("testUser"))
.thenReturn(userToReturnFromRepository);
doesn't seem to be doing anything. The function findApplicationUserByUsername returns an empty optional when it should be returning an optional of userToReturnFromRepository.
Controller:
#RestController
#RequestMapping("api/v1/exercises")
public class ExerciseController {
#Autowired
ExerciseService exerciseService;
#GetMapping
public List<Exercise> getExercises() {
List<Exercise> exercises = exerciseService.getAllExercises();
return exercises;
}
}
Service:
#Service("exerciseService")
public class ExerciseService {
#Autowired
ExerciseRepository exerciseRepository;
#Autowired
ApplicationUserRepository applicationUserRepository;
#Transactional
public List<Exercise> getAllExercises() {
Principal principal = SecurityContextHolder.getContext().getAuthentication();
Optional<ApplicationUser> applicationUser = applicationUserRepository.findApplicationUserByUsername(principal.getName());
List<Exercise> exercises = new ArrayList<>();
if(applicationUser.isPresent()){
exercises = applicationUser.get().getExercises();
}
return exercises;
}
}
The test:
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class ExerciseControllerTest {
private final MockMvc mockMvc;
private final ObjectMapper objectMapper;
#Mock
ApplicationUserRepository applicationUserRepository;
#Autowired
public ExerciseControllerTest(MockMvc mockMvc,
ApplicationUserRepository applicationUserRepository, ObjectMapper objectMapper) {
this.mockMvc = mockMvc;
this.applicationUserRepository = applicationUserRepository;
this.objectMapper = objectMapper;
}
#BeforeEach
public void initMocks() {
MockitoAnnotations.openMocks(this);
}
#Test
#WithMockUser(username = "testUser")
public void testGetExercises() throws Exception {
Exercise ex = new Exercise();
ex.setData("test");
ApplicationUser user = new ApplicationUser();
Exercise[] exercises = {ex};
List<Exercise> list = new ArrayList<Exercise>(Arrays.asList(exercises));
user.setExercises(list);
Optional<ApplicationUser> userToReturnFromRepository = Optional.of(user);
when(applicationUserRepository.findApplicationUserByUsername("testUser"))
.thenReturn(userToReturnFromRepository);
mockMvc.perform(get("/api/v1/exercises")).andExpect(status().isOk()).andExpect(jsonPath("$", hasSize(1)));
}
}
There are two conflicting things happening in your test:
You are using Mockito to init a mock implementation via reflection
You have ApplicationUserRepository being Spring injected into the Test class via the constructor.
What ends up happening is this:
spring injects applicationUserRepository into the constructor param
The applicationUserRepository field is set to the spring injected version in the constructor
Mockito inits a new applicationUserRepository mock
Mockito replaces the applicationUserRespository field with the mock (i.e. goodbye to your handle on the spring bean that your MVC setup is using!)
The easiest way I can think of to fix it is to use #MockBean instead of the #Mock combined with Constructor injection. #MockBean will instruct Spring to create the mock instance for you, use it, and provide it to you in the test.
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class ExerciseControllerTest {
private final MockMvc mockMvc;
private final ObjectMapper objectMapper;
#MockBean // User MockBean instead of Mock
ApplicationUserRepository applicationUserRepository;
#Autowired
public ExerciseControllerTest(MockMvc mockMvc, ObjectMapper objectMapper) {
this.mockMvc = mockMvc;
//remove the applicationUserRepository injection
this.objectMapper = objectMapper;
}
// remove MockitoAnnotations.openMocks(this);
//...
...
}
I have a SpringBoot app. with this test, but it does not inject and mock the classes
#RunWith(SpringRunner.class)
#WebAppConfiguration
#ContextConfiguration(locations = {
"classpath:testDatabaseContext.xml",
"classpath:testServicesContext.xml",
"classpath:servlet.xml"
})
public class TerritoriClandestiControllerTest {
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Mock
TerritoriClandestiRepository territoriClandestiRepository = mock(TerritoriClandestiRepository.class);
#InjectMocks
private TerritoriClandestiService territoriClandestiService;
List<Object[]> list;
Resource listResource = new ClassPathResource("list.txt");
#Before
public void setup() throws IOException {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.build();
list = DataLoader.readLines(listgetInputStream());
}
#Test
public void getAll() throws Exception {
when(territoriClandestiRepository.findAllBaseData(anyLong())).thenReturn(list);
mockMvc.perform(get("/terrcland")
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andDo(print())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(status().isOk())
.andExpect(jsonPath("$.*", hasSize(1)));
}
}
You can use #MockBean instead of #Mock, it will export the field as a bean in the spring context.
public class TerritoriClandestiControllerTest {
#MockBean
private TerritoriClandestiRepository territoriClandestiRepository;
}
Alternatively you can also do something like this
#RunWith(SpringRunner.class)
#WebAppConfiguration
#ContextConfiguration(locations = {
"classpath:testDatabaseContext.xml",
"classpath:testServicesContext.xml",
"classpath:servlet.xml"
}, classes = {TerritoriClandestiControllerTest.Config.class})
public class TerritoriClandestiControllerTest {
#TestConfiguration
static class Config {
#Bean
TerritoriClandestiRepository territoriClandestiRepository() {
return Mockito.mock(TerritoriClandestiRepository.class);
}
}
#Autowired
private TerritoriClandestiRepository territoriClandestiRepository;
}
I have a UserDAO that has methods like add,delete,update,getUser and getUsers (to manipulate my database). I also have a Requestmapping that I want to test via Mockito. Here is everything relevant in terms of what I have:
The test:
#RunWith(SpringJUnit4ClassRunner.class)
#WebMvcTest(value = UserController.class)
public class UserControllerTest
{
#Autowired
private MockMvc mockMvc;
#Mock
private UserDAO userDao;
#InjectMocks
private UserController userController;
#Before
public void setUp()
{
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders
.standaloneSetup(userController)
.build();
}
#Test
public void testGetAllUsersSuccess() throws Exception{
User s = new User();
List<User> users = Arrays.asList(
new User(1,"somebody", "pass", "somebody#yahoo.com"),
new User(2, "sam", "mypass", "tom#hotmail.com"));
doReturn(users).when(userDao).getUsers();
mockMvc.perform(get("/userList"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(jsonPath("$", hasSize(2)))
.andExpect(jsonPath("$[0].userID", is(1)))
.andExpect(jsonPath("$[0].name", is("somebody")))
.andExpect(jsonPath("$[0].password", is("pass")))
.andExpect(jsonPath("$[0].email", is("somebody#yahoo.com")))
.andExpect(jsonPath("$[1].userID", is(2)))
.andExpect(jsonPath("$[1].name", is("sam")))
.andExpect(jsonPath("$[1].password", is("mypass")))
.andExpect(jsonPath("$[1].email", is("tom#hotmail.com")));
verify(userDao, times(1)).getUsers();
verifyNoMoreInteractions(userDao);
}
}
UserController where I have my requestmapping:
#RestController
public class UserController {
/**
*
*
* #return list of all users
*/
#RequestMapping(value = "/userList", method = RequestMethod.GET)
public List<User> user() throws Exception {
UserDAO gettingUsers = new UserDAO();
return gettingUsers.getUsers();
}
}
the getUsers() method:
public List<User> getUsers(){
try(Session session = HibernateUtil.getSessionFactory().openSession()){
return session.createQuery("from User", User.class).getResultList();
}
}
PROBLEM: When I execute my test, a connection is made with the database (which is not what I want) instead of a fake instance of UserDAO that only returns the users list that I have made in mockito.
QUESTION: What should my code look like to be able to mock the userDAO method getUsers() such that it doesn't return the users from the database but instead returns the faked users list when I call it by requesting the get method of "/userList"?
UPDATE=
SOLUTION:
My new Controller:
#RestController
public class UserController {
private UserDAO userDAO;
public UserController (UserDAO userDAO)
{
this.userDAO = userDAO;
}
/**
*
*
* #return list of all users
*/
#GetMapping(value = "/Users", method = RequestMethod.GET)
public List<User> users() throws Exception {
return userDAO.getUsers();
}
}
Changes done in test:
//...
#MockBean
private UserDAO userDao;
....
when(userDao.getUsers()).thenReturn(users);
...//
Spring didn't find my userDAO Bean so I added the package name where I stored the bean in the ApplicationConfiguration class of mine. Doing this, Spring was able to inject the bean in the constructor.
The userDao mock is never set to the controller under test.
1) You don't want to use new to instantiate the UserDAO in your Controller :
UserDAO gettingUsers = new UserDAO();
You want to inject it as a Spring Bean.
Besides you also have to make the dependency visible because actually it is not settable from the unit test POV.
You could inject it in the constructor :
private UserDAO userDAO;
public UserController (UserDAO userDAO){
this.userDAO = userDAO;
}
2) You mock Spring bean in a running container with #MockBean from Spring Boot test not with #Mock from Mockito.
I have a test class using #WebMvcTest that looks like this
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
#WebMvcTest({HomeController.class, ShoppingCartController.class})
#Import({SecurityConfig.class, SecurityUtility.class, UserDetailsServiceImpl.class, UserRepository.class}) //#TestPropertySource("classpath:application.properties")
public class ShoppingCartControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private BookService bookService;
And now I'm getting a lot of unsatisfied bean dependency exceptions because #WebMvcTest uses slicing, My question is how can I import all the dependency without repeating #Import 20 times and how do I use #MockBean?
Previously I used
#AutoConfigureMockMvc
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = BookstoreApplications.class, properties = "classpath:application.properties")
public class ShoppingCartControllerTest {
#Autowire
private MockMvc mockMvc
#Autowired
private HomeController homeController;
#Autowired
private ShoppingCartController shoppingCartController;
And all tests were passed now I am getting a default password in the log , which means I haven't imported the Authentication Context so it used Default Authentication.
Below is the whole class as it is now
AutoConfigureMockMvc
#RunWith(SpringRunner.class)
#WebMvcTest({HomeController.class, ShoppingCartController.class})
#Import({SecurityConfig.class,UserRepository.class}) //#TestPropertySource("classpath:application.properties")
public class ShoppingCartControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private SecurityUtility securityUtility;
#MockBean
UserDetailsServiceImpl userDetailsService;
#MockBean
private BookService bookService;
#MockBean
private UserService userService;
#MockBean
private CartItemService cartItemService;
#Configuration
#Import({PropertyTestConfiguration.class})
static class ContextConfiguration {
}
#Test
public void showLoginPage() throws Exception {
mockMvc.perform(get("/login")
.accept(MediaType.TEXT_HTML)
.contentType(MediaType.TEXT_HTML)
)
.andExpect(model().attributeExists("classActiveLogin"))
.andReturn();
}
#Test
#WithMockUser(username = "V", authorities = {"USER"})
public void addItemToShoppingCart() throws Exception {
CartItem cartItem = new CartItem();
String qty = "2";
Book book = new Book();
User user = new User();
book.setId(1L);
book.getId();
cartItem.setBook(book);
when(userService.findByUsername(anyString())).thenReturn(user);
when(bookService.findOne(anyLong())).thenReturn(book);
when(cartItemService.addBookToCartItem(book, user, Integer.parseInt(qty))).thenReturn(cartItem);
ObjectMapper mapper = new ObjectMapper();
String bookAsString = mapper.writeValueAsString(book);
mockMvc
.perform(get("/shoppingCart/addItem")
.accept(MediaType.TEXT_HTML)
.contentType(MediaType.TEXT_HTML)
.param("book", bookAsString)
.param("qty", qty))
.andReturn();
}
#Test
public void checkBookDetail() throws Exception {
Book book = new Book();
book.setId(1L);
when(bookService.findOne(anyLong())).thenReturn(book);
mockMvc
.perform(get("/bookDetail")
.accept(MediaType.TEXT_HTML)
.contentType(MediaType.TEXT_HTML)
.param("id", "1"))
.andExpect(model().attributeExists("book"))
.andExpect(model().attributeExists("qty"))
.andExpect(model().attributeExists("qtyList"))
.andReturn();
}
#Test
public void showBookShelf() throws Exception {
List<Book> bookList = new ArrayList<>();
when(bookService.findAll()).thenReturn(bookList);
mockMvc.perform(get("/bookshelf")
.accept(MediaType.TEXT_HTML)
.contentType(MediaType.TEXT_HTML)
)
.andExpect(model().attributeExists("activeAll"))
.andReturn();
}
}
I also think its wrong to use #Import because I am testing two classes as I have specified in #WebMvcTest, every other bean should use #MockBean. And also I am not allowed to use #SpringBootTest And for some reason test addItemToShoppingCart passes while others fail