This post shows how to mock a method that returns unique values for different arguments in Mockito. For example, the return values vary according to the method argument.
Sample Codes To Mock
Suppose we have the following Java classes.
The first class represents a DAO that returns a pet’s directly from the database when searching by pet Id. Also, this is the Java class whose method we will mock later on to make it return different values.
1 2 3 4 5 6 7 8 9 10 11 12 13 | package com.example.mockitoreturndiffvalues; import org.springframework.stereotype.Service; /** * Pretend this uses @Repository or something similar */ @Service public class MyPetDAO { public String findPetById(long id) { return "pet name directly from the database"; } } |
Next, we have the service class, which is the target of our testing effort. It uses the DAO class to retrieve pet names from some persistent storage. Also, the service class has some business logic on the String value that the DAO method returns.
For example, when the DAO method returns null, the service method throws a RuntimeException with a specific message – “Something wrong happened while retrieving a pet by Id.”
Moreover, when the DAO method returns a String value that contains “<” (less-than character), the service method then throws another RuntimeException with a different message – “Pet name contains codes for possible XSS attack.”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | package com.example.mockitoreturndiffvalues; import org.springframework.stereotype.Service; @Service public class MyPetService { private final MyPetDAO myPetDAO; public MyPetService(MyPetDAO myPetDAO) { this.myPetDAO = myPetDAO; } public String findPetById(long id) { String pet = myPetDAO.findPetById(id); if(pet == null) { throw new RuntimeException("Something wrong happened while retrieving a pet by Id"); } if(pet == "") { throw new RuntimeException("Pet retrieved has no name - unable to returned name"); } if(pet.contains("<")) { throw new RuntimeException("Pet name contains codes for possible XSS attack"); } return pet; } } |
For our purpose, the target of our test effort is the MyPetService service class. So far, so good? Ready to move on to the next section? Very well!
Mock Method To Return Different Values
We will mock the DAO method three times with different arguments to give us various string values in our test method. For Pet 1, we mock the DAO method to return null, a blank (“”) String value for Pet 2, and JavaScript codes for Pet 3.
1 2 3 4 5 6 7 8 9 10 11 12 | // ... // Pet 1 has a null name Mockito.when(myPetDAO.findPetById(Mockito.eq(1L))).thenAnswer(invocationOnMock -> null); // Pet 2 has a blank name Mockito.when(myPetDAO.findPetById(Mockito.eq(2L))).thenAnswer(invocationOnMock -> ""); // Pet 3 has a weird name that could be a script for XSS attack Mockito.when(myPetDAO.findPetById(Mockito.eq(3L))).thenAnswer(invocationOnMock -> "<script>alert('Got ya!');</script>"); // ... |
We created three stubbings (in Mockito vocabulary), and we need to test with all three argument values – 1, 2, and 3. If we skip any of the values, Mockito throws an UnnecessaryStubbingException.
How To Fix “Please remove unnecessary stubbings or use ‘lenient’ strictness”
In the previous section, we created three stubs using the three arguments values – 1, 2, and 3. We need to test with these values. Otherwise, we get the following Mockito error.
1 2 3 4 5 6 | rg.mockito.exceptions.misusing.UnnecessaryStubbingException: Unnecessary stubbings detected. Clean & maintainable test code requires zero unnecessary code. Following stubbings are unnecessary (click to navigate to relevant line of code): 1. -> at com.example.mockitoreturndiffvalues.MyPetServiceTest.findPetById(MyPetServiceTest.java:33) Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class. |
Although we can fix this by annotating the test class or its only method as follows.
1 2 3 4 5 | // ... @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) public class MyPetServiceTest { // ... |
1 2 3 4 5 | // ... @Test @MockitoSettings(strictness = Strictness.LENIENT) void findPetById() { //... |
However, our test will not be as extensive as it should be when we do that.
Verify Mock Method Returns Different Values
Finally, we assert that the combination of stubs and arguments should throw RuntimeException with specific messages.
1 2 3 4 5 6 7 8 9 10 11 12 13 | // ... Assertions.assertThrows(RuntimeException.class, () -> { myPetService.findPetById(1L); }, "Something wrong happened while retrieving a pet by Id"); Assertions.assertThrows(RuntimeException.class, () -> { myPetService.findPetById(2L); }, "Pet retrieved has no name - unable to returned name"); Assertions.assertThrows(RuntimeException.class, () -> { myPetService.findPetById(3L); }, "Pet name contains codes for possible XSS attack"); // ... |
The complete unit test codes are as follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | package com.example.mockitoreturndiffvalues; import org.junit.jupiter.api.Assertions; 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.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) public class MyPetServiceTest { @Mock MyPetDAO myPetDAO; @InjectMocks MyPetService myPetService; @Test void findPetById() { // Pet 1 has a null name Mockito.when(myPetDAO.findPetById(Mockito.eq(1L))).thenAnswer(invocationOnMock -> null); // Pet 2 has a blank name Mockito.when(myPetDAO.findPetById(Mockito.eq(2L))).thenAnswer(invocationOnMock -> ""); // Pet 3 has a weird name that could be a script for XSS attack Mockito.when(myPetDAO.findPetById(Mockito.eq(3L))).thenAnswer(invocationOnMock -> "<script>alert('Got ya!');</script>"); Assertions.assertThrows(RuntimeException.class, () -> { myPetService.findPetById(1L); }, "Something wrong happened while retrieving a pet by Id"); Assertions.assertThrows(RuntimeException.class, () -> { myPetService.findPetById(2L); }, "Pet retrieved has no name - unable to returned name"); Assertions.assertThrows(RuntimeException.class, () -> { myPetService.findPetById(3L); }, "Pet name contains codes for possible XSS attack"); } } |
There you go! Try it out in your unit tests!