I am new to mockito / Java Spring and I tried to make a test. I have an admin controller, with this method in it :
#RequestMapping(value="/admin/users", method = RequestMethod.GET)
public ResponseEntity<List<User>>users(){
List<User> students=this.userService.getAll();
if(students.isEmpty())
return new ResponseEntity<List<User>>(HttpStatus.NO_CONTENT);
return new ResponseEntity<List<User>>(students,HttpStatus.OK);
}
Now, I tried to make a test, to see if it works, something like this :
public class AdminControllerTest {
#InjectMocks
private AdminController controller;
#InjectMocks
private UserServiceImpl userService = new UserServiceImpl();
#Mock
private UserRepository userRepository;
private MockMvc mockMvc;
#Before
public void setup(){
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
#Test
public void test() throws Exception {
User user = new User();
user.setId(5);
user.setActive(true);
user.setLastName("Test1");
user.setName("Test1");
user.setPassword("123123");
user.setRole(User.Role.student);
user.setEmail("test#gmail.com");
when(userService.save(user)).thenReturn(user);
userService.save(user);
mockMvc.perform(get("/admin/users")).andDo(print());
}
}
The problem is that I am not sure how to make the Test class add items to the repository. I tried it this way but I get NullPointerExceptions. I am guessing it is because the repository is empty and when the .getAll() method is called it returns the error.
Since you've mocked out the repository, you don't need to save things to it. All you need to do is specify what the repository would return when certain methods are called.
Instead of:
when(userService.save(user)).thenReturn(user);
... try using:
when(userRepository.findAll()).thenReturn(Collections.singletonList(user));
Why it didn't work
The reason you were getting NullPointerException was one of:
you are setting up a when on an object that isn't even a mock, and/or
you didn't provide a when for the userRepository.findAll() method, so it returned null when you called it:
List<User> students=this.userService.getAll(); <-- returned null
... which was then dereferenced and threw the NPE:
if(students.isEmpty()) <-- throws NPE when students is null
Related
I am writing tests from my springboot application. The class has a method getUserById which returns Optional<User>. This methos has an if statement that will check whether an row was returned from repository before sending a response.
Problem:
With the if statement in place, my test always throws the error in the if statement. when I remove the if statement, the test passes. What am I missing?
This is my UserServiceImpl (Class under test)
#Service
#RequiredArgsConstructor
#Transactional
#Slf4j
public class UserServiceImpl implements UserService, UserDetailsService {
#Autowired
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
#Override
public List<User> getUsers() {
log.info("Fetching users");
return userRepository.findAll();
}
#Override
public Optional<User> getUserById(Long id) {
log.info("Fetching user id: {}", id);
Optional<User> user = userRepository.findById(id);
if (!user.isPresent()) {
throw new ResourceNotFoundException(MessageUtil.ERROR_USER_NOTFOUND);
}
return user;
}
}
This is my UserServiceImplTest (test class)
#RunWith(SpringRunner.class)
#SpringBootTest
class UserServiceImplTest {
#MockBean
private UserRepository userRepositoryTest;
#InjectMocks
private UserServiceImpl userServiceTest;
#Mock
private PasswordEncoder passwordEncoder;
private List<User> userSet;
private User user1;
private User user2;
#BeforeEach
void setUp() {
userServiceTest = new UserServiceImpl(userRepositoryTest, passwordEncoder);
Set<ApplicationUserRole> roles = new HashSet<>();
roles.add(ApplicationUserRole.TEST_USER);
userSet = new ArrayList<>();
user1 = User.builder().nickname("test-nickname")
.id(1L)
.username("254701234567")
.roles(roles)
.password("password")
.build();
user2 = User.builder().nickname("test2-nickname2")
.id(2L)
.username("254701234589")
.roles(roles)
.password("password")
.build();
userSet.add(user1);
userSet.add(user2);
userSet.stream().forEach(user -> {
userServiceTest.saveUser(user);
});
}
#AfterEach
void tearDown() {
}
#Test
void testGetUsers() {
when(userServiceTest.getUsers()).thenReturn(userSet);
assertEquals(2, userServiceTest.getUsers().size());
verify(userRepositoryTest).findAll();
}
#Test
void testGetUserById() {
when(userServiceTest.getUserById(user1.getId())).thenReturn(Optional.ofNullable(user1));
assertEquals(1, user1.getId());
verify(userRepositoryTest).findById(user1.getId());
}
#Test
void testSaveUser() {
when(userServiceTest.saveUser(user1)).thenReturn(user1);
assertEquals(1L, user1.getId());
verify(userRepositoryTest).save(user1);
}
#Test
void updateUser() {
user1.setNickname("nickname-update");
when(userServiceTest.saveUser(user1)).thenReturn(user1);
assertEquals("nickname-update", user1.getNickname());
verify(userRepositoryTest).save(user1);
}
}
NOTE: Other tests work just fine
None of your tests set up the repository mock. You are trying to mock the service method instead, which will implicitly call the real method while mocking. But the service method is never called to assert correct behavior. In other words: your service's behavior is never exercised by the test, because the return value of the method calls is overwritten.
Example:
#Test
void testGetUsers() {
// repository is never mocked
// vvvvvvvvvvvvvvvvvvvvvvvvvv--- this calls the service method
when(userServiceTest.getUsers()).thenReturn(userSet);
// ^^^^^^^^^^^^^^^^^^^--- this overwrites the return value of the service method
assertEquals(2, userServiceTest.getUsers().size()); // this uses the overwritten return value
verify(userRepositoryTest).findAll();
}
To fix, you need to mock the repository (not the service) and then call the real service. It is also quite useless to assert the user's id, because the user is set up by the test, not in the classes under test.
#Test
void testGetUserById() {
// arrange
when(userRepositoryTest.getUserById(user1.getId())
.thenReturn(Optional.ofNullable(user1));
// act
Optional<User> userById = userServiceTest.getUserById(user1.getId());
// assert
assertEquals(1, user1.orElseThrow().getId());
verify(userRepositoryTest).findById(user1.getId());
}
I'd also question your usage of verify at the end of test. You are testing implementation details here. You should only be interested in the return value of your service, not which methods of the repository it is calling. Especially since you are mocking those methods anyway, so you already know with which arguments they are called; otherwise the mock would not return the configured return value in the first place.
A side-question is why your if-condition is always true and the exception always thrown. Again: incorrect/missing setup of your mocks.
#Test
void testGetUserById() {
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv--- calls real method, but the repository mock is not set up
when(userServiceTest.getUserById(user1.getId()))
.thenReturn(Optional.ofNullable(user1));
assertEquals(1, user1.getId());
verify(userRepositoryTest).findById(user1.getId());
}
You are trying to mock the service method call but the service is autowired — that doesn’t work. You can mock instead the repository method call since the repository is annotated as MockBean, that should work.
I have this Controller class that I want to test:
public class AuthController implements AuthApi {
private final UserService service;
private final PasswordEncoder encoder;
#Autowired
public AuthController(UserService service, PasswordEncoder encoder) {
this.service = service;
this.encoder = encoder;
}
#Override
public ResponseEntity<SignedInUser> register(#Valid NewUserDto newUser) {
Optional<SignedInUser> createdUser = service.createUser(newUser);
LoggerFactory.getLogger(AuthController.class).info(String.valueOf(createdUser.isPresent()));
if (createdUser.isPresent()) {
return ResponseEntity.status(HttpStatus.CREATED).body(createdUser.get());
}
throw new InsufficientAuthentication("Insufficient info");
}
This is my unit test:
#ExtendWith(MockitoExtension.class)
#JsonTest
public class AuthControllerTest {
#InjectMocks
private AuthController controller;
private MockMvc mockMvc;
#Mock
private UserService service;
#Mock
private PasswordEncoder encoder;
private static SignedInUser testSignedInUser;
private JacksonTester<SignedInUser> signedInTester;
private JacksonTester<NewUserDto> dtoTester;
#BeforeEach
public void setup() {
ObjectMapper mapper = new AppConfig().objectMapper();
JacksonTester.initFields(this, mapper);
MappingJackson2HttpMessageConverter mappingConverter = new MappingJackson2HttpMessageConverter();
mappingConverter.setObjectMapper(mapper);
mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setControllerAdvice(new RestApiErrorHandler())
.setMessageConverters(mappingConverter)
.build();
initializeTestVariables();
}
private void initializeTestVariables() {
testSignedInUser = new SignedInUser();
testSignedInUser.setId(1L);
testSignedInUser.setRefreshToken("RefreshToken");
testSignedInUser.setAccessToken("AccessToken");
}
#Test
public void testRegister() throws Exception {
NewUserDto dto = new NewUserDto();
dto.setEmail("ttn.nguyen42#gmail.com");
dto.setPassword("ThisIsAPassword");
dto.setName("ThisIsAName");
// Given
given(service.createUser(dto)).willReturn(Optional.of(testSignedInUser));
// When
MockHttpServletResponse res = mockMvc.perform(post("/api/v1/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.content(dtoTester.write(dto).getJson()).characterEncoding("utf-8").accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andReturn()
.getResponse();
// Then
assertThat(res.getStatus()).isEqualTo(HttpStatus.CREATED.value());
assertThat(res.getContentAsString()).isEqualTo(signedInTester.write(testSignedInUser).getJson());
}
}
Problem:
The test failed as I got the "Insufficient info" message, because isPresent() is never true, even with given(service.createUser(dto)).willReturn(Optional.of(testSignedInUser)) already there.
When I try to log service.createUser(dto) inside the test method, its isPresent() is always true.
When I try to log inside the controller method, it is always false.
What I have tried:
I suspect that it is because somehow my mockMvc is wrongly configured so I tried to add #AutoConfigureMockMvc but it ended up telling me that "There is no bean configured for 'mockMvc'". I tried to change the UserService mock to the implementation class of but no use.
Please help, I'm really new into Spring's unit tests. Thank you!
The problem is as you have found the Mockito mocking:
given(service.createUser(dto)).willReturn(Optional.of(testSignedInUser))
Specifically, you instruct the mocked service to return an Optional.of(testSignedInUser) if it receives a parameter that is equal to dto. However, depending on the implementation of the equals() method of NewUserDto, this may never occur. For example it returns true only if the same instance is referred to instead of comparing the values of the member variables. Consequently, when passing a dto through the mockMvc it is first serialized and then serialized again by the object mapper, so even though its member variables have the same values, the objects are not considered equal unless you also override the equals() method.
As an alternative, you can relax the mocking to return the Optional.of(testSignedInUser) if any() argument is passed:
given(service.createUser(any())).willReturn(Optional.of(testSignedInUser))
or if the argument isA() specific class:
given(service.createUser(isA(NewUserDto.class))).willReturn(Optional.of(testSignedInUser))
but generally, it is preferred from a testing perspective to be explicit to avoid false positives so for this reason I advise to double check and and / or override the NewUserDto#equals() method if possible.
Thanks to #matsev answer, I was able to solve half of the problem, but there was this thing as well:
#Controller
public class AuthController implements AuthApi {
private final UserService service;
private final PasswordEncoder encoder;
// ...
}
I left the service fields as final, which did not allow Mockito to inject or mutate the dependency. After removing final, I got the test to work now.
EDIT: Please refer to this thread for workaround Mockito, #InjectMocks strange behaviour with final fields
EDIT 2: By design, Mockito does not inject mocks to final fields: https://github.com/mockito/mockito/issues/352
Doing so violates other APIs and can cause issues. One way to fix this is to just use constructor injection, remove #InjectMocks, then you can just use final fields.
// No InjectMocks
private AuthController controller;
#Mock
private UserService service;
#Mock
private PasswordEncoder encoder;
#BeforeEach
public void setup() {
controller = new AuthController(service, encoder);
// ...
}
/*Please help I ma trying from last 2 days but no luck. tried different solution but checkEnvironment method is not returning returning TRUE value . I want method should return value without execution of method .Please ignore compilation error if have. */
enter code here #RunWith(SpringJUnit4Runner.class)
#WebAppConfiguration
#ContextConfiguration(classes = { webconfix.clss })
Public class LoginControllerTest {
#Autowired
private WebApplicationContext webcontext;
#Mock
LoginServiceImpl serviceimpl;
#InjectMocks
LoginController loginController
private MockMvc mockmvc;
ObjectMapper objMapper = new ObjectMapper();
#Before
#public void Setup() throws Exception{
MockitoAnnotations.initMocks(this);
mockMvc=MockMvcBuilders.standalonSetup(loginControllerTest)
.build;
mockMvc=MockMvcBuilders.WebAppContextSetup(webcontext)
.build;
}
#Test
public void loginTest() throws Exception{
LoginServiceImpl lgservice= spy( new LoginServiceImpl );
Login lg=new Login("test");
String requestData=objMapper.writeValueAsString(login);
Mockito.doReturn(Boolean.TRUE).when(lgservice).checkEnvironment("abc");
// Mockito.when(lgservice.checkEnvironment("abc")).thenReturn(Boolean.TRUE);
MvcResult result=mockMvc.perform(MockMvcBuilders.post("/login").content(requestdata).contentType(MediaType.APPLICATION_JSON).andExpect(status().isOk)).andReturn();
}
}
please help I ma trying from last 2 days but no luck. tried different solution but checkEnvironment method is not returning returning TRUE value . I want method should return value without execution of method .Please ignore compilation error if have.
#Mock
LoginServiceImpl serviceimpl;
Check login controller declaration. Is there any bean with type :
LoginServiceImpl
Correct me if I wrong you have interface declaration in the controller right ?
I'm trying to test service layer using Mockito with JUnit but each time when I run it return object gets nulled.
More info:
UserRepository is a plain spring data repository extending CRUDRepository.
User is a plain JPA entity.
and test:
#RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
#Mock
private UserRepository userRepository;
#InjectMocks
private UserService userService = new UserService();
private User user;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test
public void tesGetUserIdStatus() {
// given
user = new User();
user.setUserName(userName);
user.setUserId(userId);
User newUser = new User();
// when
Mockito.when(userRepository.findByUserId(userId)).thenReturn(newUser);
User result = userService.getUserById(user);
// then
Assert.assertEquals(newUser, user);
}
}
That test will end up that expected object values get nulled when actual are correct.
Service part which I want to test looks like:
#Component
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(User user) throws EntityNotFoundException {
String userId = user.getUserId();
user = userRepository.findByUserId(userId);
return user;
}
...
I see there's a design issue as well in addition to improper user of mockito. The #InjectMocks is not a good choice. It's implementation is flawed, here's what their javadocs say.
Mockito will try to inject mocks only either by constructor
injection, * setter injection, or property injection in order and as
described below. * If any of the following strategy fail, then
Mockito won't report failure; * i.e. you will have
to provide dependencies yourself.
I think it should be deprecated so developers could nicely do constructor injection. Have a look at this approach;
Refactor the UserService so you could inject the UserRepository via UserService's constructor.
For example: https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html
Then
#RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
#Mock
private UserRepository userRepository;
private UserService userService;
private User user;
#Before
public void setUp() throws Exception {
userService = new UserService(userRepository);
}
#Test
public void tesGetUserIdStatus() {
//your test code
}
This is good practice and you could test your logic (services, helpers etc) in isolation with right mocks and stubbing.
However, if you want to verify the correct injection of resources by spring ioc container and want to do operations like validation, you should consider proper Spring/Spring boot tests. Have a look at official Spring testing docs -- https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html
By findByUserId(UserId) you mean findByUserId(userId) and by
Assert.assertEquals(newUser, user) you mean Assert.assertEquals(result, user)? And where do the values userName and userId come from?
And please don't use #InjectMocks to inject an UserService
try this instead:
#Autowired
private UserService userService;
or at least
#InjectMock
private UserService userService;
Your test may fail because the Repository inside UserService cannot be injected properly and the returned result will be null.
There are a few issues in your test:
UserService is instantiated by Mockito (#InjectMocks) - do not initialise it with a new UserService instance - that is the reason you are getting NPE
since you are using MockitoJUnitRunner you do not need to explicitly call MockitoAnnotations.initMocks
when(userRepository.findByUserId(UserId)) - what is UserId?
calling Mockito.when() should be a part of the given section
in the then section you should assert the value you have obtained in the when section
I am writing JUnits for my Spring controller. In one of the cases, I am testing the Exception catched within the controller. I have mocked the service to throw a RuntimeException. Other test checks the successful output.
Individually both tests work, but when I execute both together, if the first test executes first, then the second test too throw RuntimeException. Is there anything that I need to deregister the mocked method?
(Please ignore the syntax)
class UserController {
#Autowired
UserService service;
#RequestMapping("...")
public ResponseEntity getUser(){
try{
User user = service.getUserAttributes();
return new ResponseEntity(user, HttpStatus.OK);
}
catch(Exception e){
return new ResponseEntity("Eror", HttpStatus.BAD_REQUEST);
}
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#WebAppConfiguration
#ContextConfiguration(classes=TestConfig.class)
public class UserControllerDocumentation {
#Autowired
private WebApplicationContext webApplicationContext;
private RestDocumentationResultHandler document;
private MockMvc mockMvc;
#Rule
public final RestDocumentation restDocumentation = new RestDocumentation("target/generated-snippets");
#Autowired
UserService userService;
#Before
public void setUp(){
this.document = document("{method-name}", preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()));
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext)
.apply(documentationConfiguration(this.restDocumentation)).alwaysDo(document)
.build();
}
#Test
public void textError() throws Exception {
when(userService.getUserAttributes(anyInt())).thenThrow(new RuntimeException());
this.mockMvc.perform(get("/user/xxx")
.accept("application/xml"))
.andExpect(status().isBadRequest());
}
#Test
public void textUser() throws Exception {
when(userService.getUserAttributes(anyInt())).thenReturn(mockUserObj);
this.mockMvc.perform(get("/user/10")
.accept("application/xml"))
.andExpect(status().isOk());
}
Maybe you should reset mocked object before you run each test.
You can do it using reset method.
Here are more details
http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html#17
To run this before each class you can use #Before annotation.
I don't know how #Autowired works, but obviously: your class has one instance of userService; and your textError() testcase configures that to throw an exception. And when your second test runs, that "behavior specification" is still in place and throws on you.
Thus, I see two options:
a) you use your "after" or "before" methods to reset the userService
b) each test method gets its own userService
Turns out that I need to return something for the mocked method even after throwing an exception. Added thenReturn(null) after thenThrow
#Test
public void textError() throws Exception {
when(userService.getUserAttributes(anyInt())).thenThrow(new RuntimeException()).thenReturn(null);
this.mockMvc.perform(get("/user/xxx")
.accept("application/xml"))
.andExpect(status().isBadRequest());
}