Do you need Mockito if you have Kotlin?

Salute, colleagues.

As part of Friday’s article, I suggest looking at an interesting way to create mocks in Kotlin, without using third-party libraries.

I am engaged development of add-ons for the Atlassian stack at Stiltsoft and, due to technical limitations, still (yes in 2021 and, most likely, in the next couple of years) I have to use Java 8. But, in order to keep up with progressive humanity, inside the company we try Kotlin, write tests and various experimental products on it.

However, back to the tests. Often we have a domain interface that does not belong to us, but which is actively used by our code. Moreover, the interface itself has many different methods, but in each scenario we literally use a couple of them. For example, the interface ApplicationUser

public interface ApplicationUser {
    String getKey();
 
    String getUsername();          
     
    String getEmailAddress();
 
    String getDisplayName();       
     
    long getDirectoryId();
 
    boolean isActive();
}

In different tests, we need an object of type ApplicationUser with a different set of preset fields, somewhere you need displayName and emailAddress, somewhere only username etc.

In general, we need a way to “on the fly” create objects that implement a certain interface, with the ability to arbitrarily override the methods of this object.

The simplest solution is anonymous classes

ApplicationUser user = new ApplicationUser() {
    @Override
    public String getDisplayName() {
        return "John Doe";
    }
 
    @Override
    public String getEmailAddress() {
        return "jdoe@example.com";
    }
 
    @Override
    public String toString() {
        return getDisplayName() + " <" + getEmailAddress() + ">";
    }
 
    @Override
    public String getKey() {
        return null;
    }
 
    @Override
    public String getUsername() {
        return null;
    }
 
    @Override
    public long getDirectoryId() {
        return 0;
    }
 
    @Override
    public boolean isActive() {
        return false;
    }
};

The obvious disadvantage of a simple solution, a completely insane number of lines. You can cheat a little and write an abstract class that implements all methods by default.

public abstract class AbstractApplicationUser implements ApplicationUser {
    @Override
    public String getKey() {
        return null;
    }
 
    @Override
    public String getUsername() {
        return null;
    }
 
    @Override
    public long getDirectoryId() {
        return 0;
    }
 
    @Override
    public boolean isActive() {
        return false;
    }
 
    @Override
    public String getEmailAddress() {
        return null;
    }
 
    @Override
    public String getDisplayName() {
        return null;
    }
}

and then use it.

ApplicationUser user = new AbstractApplicationUser() {
    @Override
    public String getDisplayName() {
        return "John Doe";
    }
 
    @Override
    public String getEmailAddress() {
        return "jdoe@example.com";
    }
 
    @Override
    public String toString() {
        return getDisplayName() + " <" + getEmailAddress() + ">";
    }
};

This will improve the situation with strings, but you will have to write a wrapper class for each entity of this kind.

A more advanced option is use a specialized library

ApplicationUser user = mock(ApplicationUser.class);
when(user.getDisplayName()).thenReturn("John Doe");
when(user.getEmailAddress()).thenReturn("jdoe@example.com");
 
String toString = user.getDisplayName() + " <" + user.getEmailAddress() + ">";
when(user.toString()).thenReturn(toString);

The number of lines is already in order, but the code has become more “difficult” to read and, for my taste, not very beautiful.

I propose an alternative plan: build a solution from existing Kotlin features. But first, a little theoretical digression about delegates

One of the use cases of delegation, to hang some additional functionality on a “foreign” object, and in a way invisible to the end user.

For example, we give an object ApplicationUser`a outside, but we want to send some event, every time a method is called on it getEmailAddress (). To do this, we make our own object that implements the interface ApplicationUser

public class EventApplicationUser implements ApplicationUser {
 
    private ApplicationUser delegate;
 
    public EventApplicationUser(ApplicationUser delegate) {
        this.delegate = delegate;
    }
 
    @Override
    public String getEmailAddress() {
        System.out.println("send event");
        return delegate.getEmailAddress();
    }
 
    @Override
    public String getDisplayName() {
        return delegate.getDisplayName();
    }
 
    @Override
    public String getKey() {
        return delegate.getKey();
    }
 
    @Override
    public String getUsername() {
        return delegate.getUsername();
    }
 
    @Override
    public long getDirectoryId() {
        return delegate.getDirectoryId();
    }
 
    @Override
    public boolean isActive() {
        return delegate.isActive();
    }
}

Such a construction is used as follows

public ApplicationUser method() {
    ApplicationUser user = getUser();
    return new EventApplicationUser(user);
}

So, in Kotlin there is built-in support for such use of the delegate. And instead of a sheet of code in style

@Override
public String someMethod() {
    return delegate.someMethod();
}

You can do this

class EventApplicationUser(private val user: ApplicationUser) : ApplicationUser by user {
    override fun getEmailAddress(): String {
        println("send event")
        return user.emailAddress
    }
}

And this code will work in exactly the same way as its Java counterpart. An important point, the delegation syntax also works for anonymous classes, i.e. you can do it like this, without preliminary preparation of the wrapper classes

val user = object : ApplicationUser by originalUser {
    override fun getEmailAddress(): String {
        println("send event")
        return originalUser.emailAddress
    }
}

Now you just need to somehow prepare the object. originalUserthat implements the default behavior. This is where the opportunity to create dynamic proxy

By writing a simple inline function

inline fun <reified T> proxy() = Proxy.newProxyInstance(T::class.java.classLoader, arrayOf(T::class.java), { _, _, _ -> null }) as T

we get the opportunity to write like this

val user1 = proxy<ApplicationUser>()
val user2: ApplicationUser = proxy()

Both lines do the same thing, create a dynamic proxy for the interface ApplicationUser

The difference is purely syntactic, in the first case we explicitly parameterize our proxy function() and the compiler understands that the result will be like ApplicationUser, in the second case, we frankly say that we want a variable of the type ApplicationUser and the compiler understands how to parameterize the proxy function().

It only remains to bring everything together

val user = object : ApplicationUser by proxy() {
    override fun getDisplayName() = "John Doe"
    override fun getEmailAddress() = "jdoe@example.com"
    override fun toString() = "$displayName <$emailAddress>"
}

Here we create an anonymous object with an interface ApplicationUser, then we delegate all the methods to the newly created mock and redefine only what is needed, without any wrappers / blanks for each entity, in a natural way.

ps Ideally, of course, it would be to remove the restriction on interfaces and allow to do something like that, but this requires support from the compiler

val user = proxy<ApplicationUser>() {
    override fun getDisplayName() = "John Doe"
    override fun getEmailAddress() = "jdoe@example.com"
    override fun toString() = "$displayName <$emailAddress>"
}

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *