Writing custom Mockito Matchers to match on an attribute value

Sometimes when you are testing a service call, you want to check that any intermediate calls it made (dao requests, api calls, etc) were called with the appropriate arguments, so in a sense, it is not the end result that you are interested that much, by the intermediate calls that were made by your method. In my previous blog post we had this method and its corresponding unit test:

@Component
@Accessors(chain=true)
public class ResetPasswordTokenServiceImpl implements ResetPasswordTokenService {
@Inject @Setter private RandomStringService randomStringService;
@Inject @Setter private TimeService timeService;
@Inject @Setter private PasswordResetTokenDao passwordResetTokenDao;
@Override
public PasswordResetToken generatePasswordResetTokenForUser(UUID userId){
// instead of using System.currentTimeMillis(), we externalize the call to a service, so we can track it easily
Long creationTime = timeService.getCurrentTime();
PasswordResetToken token = new PasswordResetToken();
token.setUserId(userId)
.setPasswordResetToken(randomStringService.generateRandomString())
.setIssuedTimestamp(creationTime)
.setValidTo(creationTime + DateTimeConstants.MILLIS_PER_DAY);
passwordResetTokenDao.save(token);
return token;
}
}
public class ResetPasswordTokenTest {
private ResetPasswordTokenService resetPasswordTokenService;
private RandomStringService randomStringService;
private TimeService timeService;
private PasswordResetTokenDao passwordResetTokenDao;
@Before
public void setup(){
// use mocks like me or create your own mocked implementations
randomStringService = mock(RandomStringService.class);
timeService = mock(TimeService.class);
passwordResetTokenDao = mock(PasswordResetTokenDao.class);
resetPasswordTokenService = new ResetPasswordTokenServiceImpl()
.setRandomStringService(randomStringService)
.setTimeService(timeService)
.setPasswordResetTokenDao(passwordResetTokenDao);
}
@Test
public void testResetPasswordTokenHasCorrectIssuedTimestampAndValidTo(){
UUID userId = UUID.randomId();
Long currentTime = System.currentTimeMillis();
// make the mocked service to always return the currentTime argument
when(timeService.getCurrentTime()).thenReturn(currentTime);
ResetPasswordToken token = resetPasswordTokenService.generatePasswordResetTokenForUser(userId);
// now we can easily make assertions about the issued time and the valid to
assertEquals(currentTime, token.getIssuedOn);
assertEquals(currentTime + DateTimeConstants.MILLIS_PER_DAY, token validTo);
}
}
view raw gistfile1.java hosted with ❤ by GitHub

While we tested the outcome of the service call, we did not test whether the intermediate calls it made, namely line 17:

passwordResetTokenDao.save(token);

have been called with the appropriate parameters. For example, if that part:

token.setUserId(userId)
.setPasswordResetToken(randomStringService.generateRandomString())
.setIssuedTimestamp(creationTime)
.setValidTo(creationTime + DateTimeConstants.MILLIS_PER_DAY);
passwordResetTokenDao.save(token);
view raw gistfile1.java hosted with ❤ by GitHub

was to be

passwordResetTokenDao.save(token);
token.setUserId(userId)
.setPasswordResetToken(randomStringService.generateRandomString())
.setIssuedTimestamp(creationTime)
.setValidTo(creationTime + DateTimeConstants.MILLIS_PER_DAY);
view raw gistfile1.java hosted with ❤ by GitHub

The unit test would not fail, but the information that was stored in your db would certainly not be correct. So how do you rewrite your unit test to meet the newly imposed demands? People familiar with Mockito would immediately know the answer: argument matchers.

What is a Mockito argument matcher? It is simply a class that handles that very specific case: when you want to test if one or more of your mocks has been called one or more times with a specific argument. Sometimes, you want to also test the order of calling of your mocks, with Mockito you can do that as well; It is where the library shines with its intuitive API and highly customizable matchers.

You can also use Mockito’s provided argument captor but it results in slightly more verbose code and is not really the on-the spot argument testing that you want to achieve here (cause you have to later do your asserts) and code using argument captors has a different flow (you have to call capture(), etc..) and doesn’t look consistent with your other unit tests. I would argue that in most cases the equality matcher provided by Mockito would be enough for your unit tests, but in case you really really really want to test if an intermediate mock has been called with arguments which have only a subset of properties that you care about, a custom argument matcher results in a more fluid and consistent code than using an argument captor.

Let us rewrite the unit test from above so apart from checking the end result, we check if the dao mock was called with the appropriate arguments:

@Component
@Accessors(chain=true)
public class ResetPasswordTokenServiceImpl implements ResetPasswordTokenService {
@Inject @Setter private RandomStringService randomStringService;
@Inject @Setter private TimeService timeService;
@Inject @Setter private PasswordResetTokenDao passwordResetTokenDao;
@Override
public PasswordResetToken generatePasswordResetTokenForUser(UUID userId){
// instead of using System.currentTimeMillis(), we externalize the call to a service, so we can track it easily
Long creationTime = timeService.getCurrentTime();
PasswordResetToken token = new PasswordResetToken();
token.setUserId(userId)
.setPasswordResetToken(randomStringService.generateRandomString())
.setIssuedTimestamp(creationTime)
.setValidTo(creationTime + DateTimeConstants.MILLIS_PER_DAY);
passwordResetTokenDao.save(token);
return token;
}
}
public class ResetPasswordTokenTest {
private ResetPasswordTokenService resetPasswordTokenService;
private RandomStringService randomStringService;
private TimeService timeService;
private PasswordResetTokenDao passwordResetTokenDao;
@Before
public void setup(){
// use mocks like me or create your own mocked implementations
randomStringService = mock(RandomStringService.class);
timeService = mock(TimeService.class);
passwordResetTokenDao = mock(PasswordResetTokenDao.class);
resetPasswordTokenService = new ResetPasswordTokenServiceImpl()
.setRandomStringService(randomStringService)
.setTimeService(timeService)
.setPasswordResetTokenDao(passwordResetTokenDao);
}
@Test
public void testResetPasswordTokenHasCorrectIssuedTimestampAndValidTo(){
UUID userId = UUID.randomId();
Long currentTime = System.currentTimeMillis();
String randomString = "someRandomString";
// make the mocked service to always return the currentTime argument
when(timeService.getCurrentTime()).thenReturn(currentTime);
when(randomStringService.generateRandomString()).thenReturn(randomString);
ResetPasswordToken token = resetPasswordTokenService.generatePasswordResetTokenForUser(userId);
//mockito will use equality for matching, so you can easily create an instance of what you expect your mock to be called with it and later
//use it in a matcher
PasswordResetToken expectedToken = new PasswordResetToken()
.setUserId(userId)
.setPasswordResetToken(randomString)
.setIssuedTimestamp(creationTime)
.setValidTo(creationTime + DateTimeConstants.MILLIS_PER_DAY);
// verify that the passwordResetTokenDao has been called once with the expected value
verify(passwordResetTokenDao, times(1)).save(eq(expectedToken));
// now we can easily make assertions about the issued time and the valid to
assertEquals(currentTime, token.getIssuedOn);
assertEquals(currentTime + DateTimeConstants.MILLIS_PER_DAY, token validTo);
}
}
view raw gistfile1.java hosted with ❤ by GitHub

This all should look very familiar to most Java developers. Imagine, however, that you wanted to test that only a certain attribute of the bean, for example, its userId, was set to its appropriate value, and don’t care about the others? Relying on lombok generated equalsAndHashCode could do the trick, but then you would have to create the bean with ALL its properties set, because Mockito uses equality to check if your argument matches your expectations. But that is such a waste! You would have to create a whole bean and set all its attributes properly, but you just wanted to test that the passwordResetTokenDao was called when an object that has its userId property set. You can indeed override the equals method of your newly created bean, but that is ugly. Surely there must be an other way?

The other way, of course, exists, and it involves creating a custom matcher. Such a matcher has to be able to check if the instance of a class you called a mock with had its desired attributes set. Keep in mind that in some circumstances it is better to just override the equals method, but in the case that you want to test if a very specific property or a set of properties are set, then it might look better if you have a matcher for only those.

Here is the code of a custom argument matcher that matches a single property:

public class HasPropertyOfCertainValueMatcher<C, P> extends ArgumentMatcher<C> {
private final String propName;
private final P propValue;
public HasPropertyOfCertainValueMatcher(String propName, P propValue, Class<C> claszz) {
this.propName = propName;
this.propValue = propValue;
}
@Override
public boolean matches(Object argument) {
try {
Object privateField = PrivateFieldUtils.getPrivateFieldIncludeInherited(argument, propName);
if (!privateField.getClass().equals(getPropClass()) ){
return false;
}
@SuppressWarnings("unchecked")
P castedObj = (P) privateField;
if ( propValue == null && castedObj != null ){
return false;
} else if ( propValue instanceof Object[] ){
return Arrays.equals((Object[])propValue, (Object[])castedObj);
} else {
return propValue.equals(castedObj);
}
} catch (Exception e) {
return false;
}
}
@SuppressWarnings("unchecked")
private Class<P> getPropClass() {
return (Class<P>) propValue.getClass();
}
@Override
public String toString() {
return "<Has property of certain value (propName: " + propValue + ") matcher>";
}
@Override
public void describeTo(org.hamcrest.Description description) {
description.appendText(this.toString());
}
}
view raw gistfile1.java hosted with ❤ by GitHub

The PrivateFieldUtils thing is just some reflection class that gets a property by its name in a class by reflection. There are multiple libraries around that can do that for you or you can quickly write your own. However, what is more interesting is how to use the newly created matcher. You must call Mockito’s argThat method to match against your class. SInce this would result in a very verbose code, we can define a simple class called Shorthands in which we return an instance of the argThat matcher:

public class Shorthands {
/**
*
* @param <C>
* @param <P>
* @param propName
* @param propertyValue
* @param pClass the class of the object we want to check
* @return
*/
public static <C, P> C hasPropertyOfCertainValue(String propName, P propertyValue, Class<C> pClass ){
return argThat(new HasPropertyOfCertainValueMatcher<C, P>(propName, propertyValue, pClass));
}
}
view raw gistfile1.java hosted with ❤ by GitHub

And here is a bunch of unit tests to show you how to use the matchers (Shorthands and Mockito have their corresponding methods statically imported):

public class HasPropertyOfCertainValueMatcherTest {
private InnerClassToBeTested instance;
@Test
public void test(){
instance = mock(InnerClassToBeTested.class);
InternalClassContainingProp classWithProp = new InternalClassContainingProp("somePropValue", null);
instance.callMethodRequiringSomeClassWithSomeProp(classWithProp);
// test that the first property of our class was called with an argument that has its properties set accordingly
verify(instance,times(1)).callMethodRequiringSomeClassWithSomeProp(hasPropertyOfCertainValue("someProp", "somePropValue", InternalClassContainingProp.class));
}
@Test
public void test2(){
instance = mock(InnerClassToBeTested.class);
InternalClassContainingProp classWithProp = new InternalClassContainingProp("somePropValue", null);
instance.callMethodRequiringSomeClassWithSomeProp(classWithProp);
// test that the first property of our class was called with an argument that has its properties set accordingly
verify(instance,never()).callMethodRequiringSomeClassWithSomeProp(hasPropertyOfCertainValue("someProp", "someOther", InternalClassContainingProp.class));
}
@Test
public void test3(){
instance = mock(InnerClassToBeTested.class);
InternalClassContainingProp classWithProp = new InternalClassContainingProp("somePropValue", new Point(5,6));
instance.callMethodRequiringSomeClassWithSomeProp(classWithProp);
// test that the first property of our class was called with an argument that has its properties set accordingly
verify(instance,times(1)).callMethodRequiringSomeClassWithSomeProp(hasPropertyOfCertainValue("someProp", "somePropValue", InternalClassContainingProp.class));
verify(instance,times(1)).callMethodRequiringSomeClassWithSomeProp(hasPropertyOfCertainValue("someOtherProp", new Point(5,6), InternalClassContainingProp.class));
}
// a simple class that has a method containing a single argument. We want to check if that argument had the appropriate properties set at moment of calling
class InnerClassToBeTested {
public void callMethodRequiringSomeClassWithSomeProp(InternalClassContainingProp classWithProp) {
}
}// a class that would be used as an argument of the InnerClassToBeTested. This class has two properites, for which it can be tested
class InternalClassContainingProp {
private String someProp;
private Point someOtherProp;
public InternalClassContainingProp(String someProp, Point someOtherProp){
this.someProp = someProp;
this.someOtherProp = someOtherProp;
}
public Point getSomeOtherProp() {
return someOtherProp;
}
public String getSomeProp() {
return someProp;
}
}
}
view raw gistfile1.java hosted with ❤ by GitHub

Then you would be able to rewrite the Password Reset Token unit test by checking if the dao was called with the validTo and issuedTimestamp properties appropriately set:

@Component
@Accessors(chain=true)
public class ResetPasswordTokenServiceImpl implements ResetPasswordTokenService {
@Inject @Setter private RandomStringService randomStringService;
@Inject @Setter private TimeService timeService;
@Inject @Setter private PasswordResetTokenDao passwordResetTokenDao;
@Override
public PasswordResetToken generatePasswordResetTokenForUser(UUID userId){
// instead of using System.currentTimeMillis(), we externalize the call to a service, so we can track it easily
Long creationTime = timeService.getCurrentTime();
PasswordResetToken token = new PasswordResetToken();
token.setUserId(userId)
.setPasswordResetToken(randomStringService.generateRandomString())
.setIssuedTimestamp(creationTime)
.setValidTo(creationTime + DateTimeConstants.MILLIS_PER_DAY);
passwordResetTokenDao.save(token);
return token;
}
}
public class ResetPasswordTokenTest {
private ResetPasswordTokenService resetPasswordTokenService;
private RandomStringService randomStringService;
private TimeService timeService;
private PasswordResetTokenDao passwordResetTokenDao;
@Before
public void setup(){
// use mocks like me or create your own mocked implementations
randomStringService = mock(RandomStringService.class);
timeService = mock(TimeService.class);
passwordResetTokenDao = mock(PasswordResetTokenDao.class);
resetPasswordTokenService = new ResetPasswordTokenServiceImpl()
.setRandomStringService(randomStringService)
.setTimeService(timeService)
.setPasswordResetTokenDao(passwordResetTokenDao);
}
@Test
public void testResetPasswordTokenHasCorrectIssuedTimestampAndValidTo(){
UUID userId = UUID.randomId();
Long currentTime = System.currentTimeMillis();
// make the mocked service to always return the currentTime argument
when(timeService.getCurrentTime()).thenReturn(currentTime);
ResetPasswordToken token = resetPasswordTokenService.generatePasswordResetTokenForUser(userId);
// verify that the dao had properly received a Reset Token Password with its validTo and issuedTimestamp set
verify(passwordResetTokenDao, times(1)).save(hasPropertyOfCertainValue("issuedTimestamp", currentTime, ResetPasswordToken.class));
verify(passwordResetTokenDao, times(1)).save(hasPropertyOfCertainValue("validTo", currentTime + DateTimeConstants.MILLIS_PER_DAY, ResetPasswordToken.class));
// now we can easily make assertions about the issued time and the valid to
assertEquals(currentTime, token.getIssuedOn);
assertEquals(currentTime + DateTimeConstants.MILLIS_PER_DAY, token validTo);
}
}
view raw gistfile1.java hosted with ❤ by GitHub

Now you have tested that the dao was called with the correct argument. Of course, you could have overriden the equality of your bean, or used another bean that has only certain properties set, but that would be ugly and less easy to read.

2 thoughts on “Writing custom Mockito Matchers to match on an attribute value

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.