How to test custom exception with Mockito - java

I am trying to learn unit testing with Mockito. This is a method I wrote to authenticate the users that tries to log in:
public class AuthenService {
private UserRepo userRepo;
public User authenticate(String username, String password) throws UserNotFoundException {
User user = null;
if (userRepo.validate(username, password)) {
user = userRepo.findUsername(username);
return user;
} else {
throw new UserNotFoundException("User not found!");
}
}
}
userRepo is an interface with these two abstract methods:
public interface UserRepo{
boolean validate(String username, String password);
User findUsername(String username);
}
This is my testing code:
public class Test {
AuthenService as;
UserRepo ur;
#BeforeEach
public void init() {
ur = mock(UserRepo.class);
as = new AuthenService(ur);
}
#Test
public void authenticate() throws UserNotFoundException {
when(as.authenticate("abc", "123")).thenThrow(new UserNotFoundException("User not found!"));
}
}
I suppose as.authenticate("abc", "123") should throw an exception because the DB is empty right now. But I think I am wrong, and I have no idea how to fix it. Can anyone please explain to me? Thank you.

The way code is written for the test is not correct. The when(...) method is used to specify a behavior for a mock object. In this case, you are trying to use it to specify that the authenticate method should throw an exception. This is not the correct way to use the when(...) method.
Instead, you should use the thenThrow(...) method to specify that the mocked UserRepo should throw a UserNotFoundException when the validate method is called.
#Test
public void authenticate() throws UserNotFoundException {
//given
when(ur.validate("abc", "123"))
.thenThrow(new UserNotFoundException("User not found!"));
//when
assertThrows(
UserNotFoundException.class,
() -> as.authenticate("abc", "123"));
}
We can furhter improve this test since we are right now mixing mock setup with validation:
when(ur.validate("abc", "123"))
.thenThrow(new UserNotFoundException("User not found!"));
This line instructs the mock to only throw the exception when method validate(...) is called with those exact parameters. We can refactor the test, so that validate(...) throws on any parameters, and validate later on that validate(...) was called with the expected parameters:
#Test
public void authenticate() throws UserNotFoundException {
//given
// React on any strings passed along as parameters
// | |
// v v
when(ur.validate(anyString(), anyString()))
.thenThrow(new UserNotFoundException("User not found!"));
//when
assertThrows(
UserNotFoundException.class,
() -> as.authenticate("abc", "123"));
//then
validate(ur).validate("abc", "123");
// ^ ^
// | |
// | This is the "validate(...)" method we mocked
// This "validate(...)" method is provided by mockito
}

You are stubbing instead of asserting in your test, so basically you are not testing anything. A test should include at least one assertion, so it should look something similar to this:
assertThrows(UserNotFoundException.class, () -> as.authenticate("abc", "123"));

One unit test would be to expect an exception if the validation fails. To that, consider (working example here:
#Test
public void authenticate() throws UserNotFoundException {
when(ur.validate("abc", "123")).thenReturn(false);
// test
assertThrows(UserNotFoundException.class, () ->as.authenticate("abc", "123"));
}
Note: updated in accordance with comment, re: JUnit 5.

Related

Why is the test fails though being called with right parameters?

I have test, I simplified it to the point it looks like this ( code below ). Objectmapper works fine in tests in the same class.
#Test
void shouldUpdateVote_whenPut() throws Exception {
Mockito.when(voteService.update(5, VOTE_CREATION_ADRIANO)).thenReturn(VOTE_RESPONSE_ADRIANO);
mockMvc.perform(
put("/vote/admin/5")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(VOTE_CREATION_ADRIANO)))
.andExpect(status().isOk())
.andExpect(jsonPath("$", notNullValue()));
}
Test fails every time with
java.lang.AssertionError: No value at JSON path "$"
I decided to check if the method is called with needed parameters.
#Test
void shouldUpdateVote_whenPut() throws Exception {
Mockito.when(voteService.update(5, VOTE_CREATION_ADRIANO)).thenReturn(VOTE_RESPONSE_ADRIANO);
mockMvc.perform(
put("/vote/admin/5")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(VOTE_CREATION_ADRIANO)));
Mockito.verify(voteService, times(1)).update(eq(5), refEq(VOTE_CREATION_ADRIANO));
}
And it is successfull. The question is, why does the test fail? Is the problem with mockito.when?
Here is the controller method under test. Controller works fine, I've checked it many times.
#PutMapping("/{id}")
public VoteResponseDTO updateAnyVote(#PathVariable int id, #RequestBody #Valid VoteCreationDTO voteCreationDTO,
BindingResult bindingResult) {
throwExceptionIfBindingResultHasErrors(bindingResult);
return voteService.update(id, voteCreationDTO);
}

Mockito Test failed with when/thenReturn

I have written the controller test. However I am not able to pass the test. Is there anything wrong with the way I have written the test or the service method?
This is the test I'm running:
#Test
void controller_getUserTest() throws Exception {
UserEntity user = getUser(); //dummy user from getUser() helper method
when(userService.getUser("jeremy")).thenReturn(user);
this.mockMvc.perform(get("/user/jeremy")).andDo(print())
.andExpect(status().isOk());
}
This is the controller method that I'm testing:
#GetMapping("/{username}")
public ResponseEntity<UserEntity> getUser(#PathVariable String username) {
System.out.println(username);
UserEntity theUser = userService.getUser(username);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
if(theUser == null) {
String errorMessage = "User Not Found";
return new ResponseEntity(errorMessage,headers,HttpStatus.NOT_FOUND);
}
return new ResponseEntity<UserEntity>(theUser,headers,HttpStatus.OK);
}
This is the service method that was being mocked in when():
#Override
#Transactional
public UserEntity getUser(String username) {
UserEntity probe = new UserEntity();
probe.setUsername(username);
probe.setEnabled(true);
ExampleMatcher matcher = ExampleMatcher.matching().withIgnoreCase();
Example<UserEntity> example = Example.of(probe,matcher);
Optional<UserEntity> result = userRepository.findOne(example);
return result.isPresent()? result.get() : null;
The error message when the test failed:
org.mockito.exceptions.misusing.WrongTypeOfReturnValue:
UserEntity cannot be returned by findOne()
findOne() should return Optional
***
If you're unsure why you're getting above error read on.
Due to the nature of the syntax above problem might occur because:
1. This exception *might* occur in wrongly written multi-threaded tests.
Please refer to Mockito FAQ on limitations of concurrency testing.
2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies -
- with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.
I also have a getUser() method defined to init a dummy user for tests
Edit:
I realised that I wasnt using a mock instance of the UserService class, however, when I tried to use one, the error still appear.
I realised that the UserService instance I was using is an actual service instance instead of a mocked one, thats why it failed the test.

How do i unit test an exception that's being thrown by an "update user" endpoint?

I'm currently unit testing the endpoints of a Spring Boot CRUD RESTful API and i have the following "Update user by its id" endpoint that's composed of a controller and a service to implement its logic :
Update controller (it's mainly calling the logic of the service and defining some guidelines) :
#RestController
#RequestMapping("/users/{id}")
public class UpdateUserController {
#Autowired
UpdateUserService service;
#PutMapping
#ResponseStatus(HttpStatus.OK)
public ResponseEntity<User> updateUser_whenPutUser(#RequestBody User user, #PathVariable Long id) {
return ResponseEntity.ok().body(service.updateUser(id, user));
}
}
Update service :
#Service
public class UpdateUserService {
#Autowired
UserRepository repository;
public User updateUser(Long id, User user) {
repository.findById(id)
.orElseThrow(() -> new UserNotFoundException(id));
user.setId(id);
return repository.save(user);
}
}
While everything seems to be working fine until now, i'd appreciate if someone could tell me what i could improve in the code above. Anyways, my controllers throw exception unit test is the problem i can't seem to solve :
#RunWith(SpringRunner.class)
#WebMvcTest(UpdateUserController.class)
public class UpdateUserControllerTest {
#Autowired
private MockMvc mvc;
#MockBean
private UpdateUserService updateUserService;
#Test
public void updateUser_whenPutUser() throws Exception {
User user = new User();
user.setName("Test Name");
user.setId(89L);
given(updateUserService.updateUser(user.getId(), user)).willReturn(user);
ObjectMapper mapper = new ObjectMapper();
mvc.perform(put("/users/" + user.getId().toString())
.content(mapper.writeValueAsString(user))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("name", is(user.getName())));
}
#Test
public void should_throw_exception_when_user_doesnt_exist() throws Exception {
User user = new User();
user.setId(89L);
user.setName("Test Name");
Mockito.doThrow(new UserNotFoundException(user.getId())).when(updateUserService).updateUser(user.getId(), user);
mvc.perform(put("/users/" + user.getId().toString())
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound());
}
}
Although the updateUser_whenPutUser is passing, the exceptions unit test is resulting in :
java.lang.AssertionError: Status expected:<404> but was:<400>
Expected :404
Actual :400
Which means it's getting a 400 response status instead of the expected 404 thrown by the exception.
Curiously, if i change my #WebMvcTest(UpdateUserController.class) to #WebMvcTest(UpdateUserService.class) it passes, but then my controllers main unit test fails. No idea why.
Would appreciate if someone could help with this one.
First you can give your Test Annotation an expected type. Like this
#Test(expected = NotFoundException.class)
public void test(){
...
}
The reason why you get an 400 instead of an 404 is hard to say. I would suggest it has something to do with the status() methode. Maybe you can provide some more of the stacktrace? Or i think you need to debug your tests to find out where the exception happens.
I've found the solution :
The reason it was returning a 400 response status error is because no message body was being sent.
This specific endpoint requires a message body written in JSON on the request :
#Test
public void should_throw_exception_when_user_doesnt_exist() throws Exception {
User user = new User();
user.setId(89L);
user.setName("Test Name");
Mockito.doThrow(new UserNotFoundException(user.getId())).when(updateUserService).updateUser(user.getId(), user);
ObjectMapper mapper = new ObjectMapper();
mvc.perform(put("/users/" + user.getId().toString())
.content(mapper.writeValueAsString(user))
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isNotFound());
}

Mockito throwing a NullpointerException on using a mock

I'm trying to create test cases for a webservice but I'm getting nullpointerexception. This is the web service:
#Path("friendservice")
public class FriendWebService {
private static final Logger logger = Logger.getLogger(FriendWebService.class);
#EJB
private FriendRequestServiceInterface friendRequestService;
#GET
#Path("friendrequest")
#Produces(MediaType.TEXT_PLAIN)
public String createFriendRequest(
#Context HttpServletRequest request) {
logger.info("createFriendRequest called");
String result = "false";
User user = (User) request.getSession().getAttribute("user");
User otherUser = (User) request.getSession().getAttribute("profileuser");
if ((user != null) && (otherUser != null)) {
logger.info("Got two users from session, creating friend request.");
if (friendRequestService.createFriendRequest(user, otherUser)) {
result = "true";
}
}
return result;
}
}
This is my test class:
public class FriendWebServiceTest {
#Mock
FriendRequestServiceInterface FriendRequestService;
#Mock
Logger mockedLogger = mock(Logger.class);
#Mock
HttpServletRequest mockedRequest = mock(HttpServletRequest.class);
#Mock
HttpSession mockedSession = mock(HttpSession.class);
#Mock
User mockedUser = mock(User.class);
#Mock
User mockedOtherUser = mock(User.class);
#InjectMocks
FriendWebService friendWebService = new FriendWebService();
#Before
public void setUp() throws Exception {
}
#Test
public void testCreateFriendRequest() throws Exception {
when(mockedRequest.getSession()).thenReturn(mockedSession);
when(mockedSession.getAttribute("user")).thenReturn(mockedUser);
when(mockedSession.getAttribute("profileuser")).thenReturn(mockedOtherUser);
when(FriendRequestService.createFriendRequest(mockedUser, mockedOtherUser)).thenReturn(true);
assertTrue(friendWebService.createFriendRequest(mockedRequest) == "true");
}
The NullPointerException occurs at "when(FriendRequestService.createFriendRequest(mockedUser, mockedOtherUser)).thenReturn(true);"
What am I doing wrong?
You are chaining method calls on your mocked instance:
#Mock
HttpServletRequest mockedRequest = mock(HttpServletRequest.class);
First of all, you do not need to do both, either use the #Mock annotation or the mock method. Like this, you first assign a Mock and then replace this instance with another mock. I recommend the annotation as it adds some context to the mock such as the field's name. This might already cause your NullPointerException as you however never activate the annotations by calling:
MockitoAnnotations.initMocks(this);
as you do not consequently mock all instances with both measures so far. However, even doing so will further result in your exception, so let's move ahead.
Within friendWebService.createFriendRequest(mockedRequest) you call:
User user = (User) request.getSession().getAttribute("user");
User otherUser = (User) request.getSession().getAttribute("profileuser");
where you call a method on two mocks for which you did not specify any behavior. These mocks do by default return null. You need to specify behavior for this such as:
when(request.getSession()).thenReturn(myMockedSession);
before performing this chained call. Based on this, you can then specify how to react to calls on this mocked instance such as returning your user mocks.
Instead of calling initMocks, You probably need to annotate with #RunWith(MockitoJUnitRunner.class) to your FriendWebServiceTest class.
You can also try adding #RunWith(MockitoJUnitRunner.class) or #ExtendWith(MockitoExtension.class) annotations to your FriendWebServiceTest class for JUnit4 and JUnit5 respectively.

MockMVC how to test exception and response code in the same test case

I want to assert that an exception is raised and that the server returns an 500 internal server error.
To highlight the intent a code snippet is provided:
thrown.expect(NestedServletException.class);
this.mockMvc.perform(post("/account")
.contentType(MediaType.APPLICATION_JSON)
.content(requestString))
.andExpect(status().isInternalServerError());
Of course it dosen't matter if I write isInternalServerError or isOk.
The test will pass regardless if an exception is thrown below the throw.except statement.
How would you go about to solve this?
If you have an exception handler and you want to test for a specific exception, you could also assert that the instance is valid in the resolved exception.
.andExpect(result -> assertTrue(result.getResolvedException() instanceof WhateverException))
UPDATE (gavenkoa) Don't forget to inject #ExceptionHandler annotated methods to the test context or exception will occur at .perform() instead of capturing it with .andExpect(), basically register:
#ControllerAdvice
public class MyExceptionHandlers {
#ExceptionHandler(BindException.class)
public ResponseEntity<?> handle(BindException ex) { ... }
}
with #Import(value=...) or #ContextConfiguration(classes=...) or by other means.
You can get a reference to the MvcResult and the possibly resolved exception and check with general JUnit assertions...
MvcResult result = this.mvc.perform(
post("/api/some/endpoint")
.contentType(TestUtil.APPLICATION_JSON_UTF8)
.content(TestUtil.convertObjectToJsonBytes(someObject)))
.andDo(print())
.andExpect(status().is4xxClientError())
.andReturn();
Optional<SomeException> someException = Optional.ofNullable((SomeException) result.getResolvedException());
someException.ifPresent( (se) -> assertThat(se, is(notNullValue())));
someException.ifPresent( (se) -> assertThat(se, is(instanceOf(SomeException.class))));
You can try something as below -
Create a custom matcher
public class CustomExceptionMatcher extends
TypeSafeMatcher<CustomException> {
private String actual;
private String expected;
private CustomExceptionMatcher (String expected) {
this.expected = expected;
}
public static CustomExceptionMatcher assertSomeThing(String expected) {
return new CustomExceptionMatcher (expected);
}
#Override
protected boolean matchesSafely(CustomException exception) {
actual = exception.getSomeInformation();
return actual.equals(expected);
}
#Override
public void describeTo(Description desc) {
desc.appendText("Actual =").appendValue(actual)
.appendText(" Expected =").appendValue(
expected);
}
}
Declare a #Rule in JUnit class as below -
#Rule
public ExpectedException exception = ExpectedException.none();
Use the Custom matcher in test case as -
exception.expect(CustomException.class);
exception.expect(CustomException
.assertSomeThing("Some assertion text"));
this.mockMvc.perform(post("/account")
.contentType(MediaType.APPLICATION_JSON)
.content(requestString))
.andExpect(status().isInternalServerError());
P.S.: I have provided a generic pseudo code which you can customize as per your requirement.
I recently encountered the same error, and instead of using MockMVC I created an integration test as follows:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration(classes = { MyTestConfiguration.class })
public class MyTest {
#Autowired
private TestRestTemplate testRestTemplate;
#Test
public void myTest() throws Exception {
ResponseEntity<String> response = testRestTemplate.getForEntity("/test", String.class);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode(), "unexpected status code");
}
}
and
#Configuration
#EnableAutoConfiguration(exclude = NotDesiredConfiguration.class)
public class MyTestConfiguration {
#RestController
public class TestController {
#GetMapping("/test")
public ResponseEntity<String> get() throws Exception{
throw new Exception("not nice");
}
}
}
this post was very helpful: https://github.com/spring-projects/spring-boot/issues/7321
In your controller:
throw new Exception("Athlete with same username already exists...");
In your test:
try {
mockMvc.perform(post("/api/athlete").contentType(contentType).
content(TestUtil.convertObjectToJsonBytes(wAthleteFTP)))
.andExpect(status().isInternalServerError())
.andExpect(content().string("Athlete with same username already exists..."))
.andDo(print());
} catch (Exception e){
//sink it
}

Categories