Unit testing default methods in interfaces

Java 8 has introduced a nice feature, default (so-called “defender”) methods in interfaces. These methods are implemented in the interface, but can be overridden.

Now how can we unit-test these default methods?

A naive approach would be: in your unit test class, just implement the interface and test the default method on that interface. Something like this:

public interface IntProvider{
    int getInt();
    default int getIntTimesTwo(){
        return getInt() * 2;
    }
}
@Test
public void testGetIntTimesTwo(){
 final int n = 4;
 IntProvider ip = () -> n;
 assertThat(ip.getIntTimesTwo(), is(n * 2));
}

This example is relatively pain-free, especially since the interface is functional and hence can be expressed as a lambda. If things are that simple, count your blessings and stop reading.

One problem I do see, however, is that introducing a new non-default method to the interface will break this test, in fact the test won’t even compile. I would argue that this is a sign of tight coupling, and hence bad software design.

One suggestion would be to use Mockito to mock the interface. It’s a great suggestion, and of course the first one I thought of. And in Mockito 2.x or higher, you can do it:

@Test
public void testGetIntTimesTwoWithMockito(){
    final int n = 4;
    IntProvider ip = Mockito.mock(IntProvider.class);
    Mockito.when(ip.getInt()).thenReturn(n);
    Mockito.when(ip.getIntTimesTwo()).thenCallRealMethod();
    assertThat(ip.getIntTimesTwo(), is(n * 2));
}

Nice!

Now my problem is: unfortunately I have to use Mockito 1.x, which doesn’t have this functionality. So another solution is to roll your own version. To this effect, we’ll look at JDK dynamic proxies. Since Java 1.5, you can instantiate an interface with an InvocationHandler, and Java 8 makes this look (relatively) painless:

@Test @Ignore // running this may crash your IDE
public void testGetIntTimesTwoWithProxies_thisDoesntWork() {
    final int n = 4;
    IntProvider ip = (IntProvider) Proxy.newProxyInstance(
            IntProvider.class.getClassLoader(),
            new Class<?>[]{IntProvider.class},
            (proxy, method, args) -> { // lambda InvocationHandler
                if (method.getName().equals("getInt")) {
                    return n;
                } else {
                    return method.invoke(proxy, args);
                }
            }
    );
    assertThat(ip.getIntTimesTwo(), is(n * 2));
}

Wouldn’t it be great if that worked? But it doesn’t. It gives you a StackOverflowError, as the else block will keep executing the method on the proxy and never get at the actual default method. Also, the InvocationHandler needs to take care needs to take care of the standard boilerplate methods as well, or you will get some very weird surprises when calling equals(), hashCode() or toString(). To that end, Guava provides a convenience class called AbstractInvocationHandler that handles the standard methods with sane defaults. Unfortunately that means we can’t use lambdas:

@Test @Ignore // running this may crash your IDE
public void testGetIntTimesTwoWithProxies_guava_thisStillDoesntWork() {
    final int n = 4;
    IntProvider ip = (IntProvider) Proxy.newProxyInstance(

            IntProvider.class.getClassLoader(),
            new Class<?>[]{IntProvider.class},
            new AbstractInvocationHandler() {
                protected Object handleInvocation(
                    Object proxy, Method method, Object[] args
                ) throws Exception {
                    if (method.getName().equals("getInt")) {
                        return n;
                    } else {
                        return method.invoke(proxy, args);
                    }
                }
            }
    );
    assertThat(ip.getIntTimesTwo(), is(n * 2));
}

So now we have working equals and hashCode, but still no default methods.

To get these to work, we have to use a MethodHandle, which we can obtain as follows:

return MethodHandles.lookup()
    .in(declaringClass)
    .unreflectSpecial(method, declaringClass)
    .bindTo(proxy)
    .invokeWithArguments(args);

[Source]

So we can rewrite our test like this:

@Test
public void testGetIntTimesTwoWithMethodHandle_thisSometimesWorks() {
    final int n = 4;
    IntProvider ip = (IntProvider) Proxy.newProxyInstance(
            IntProvider.class.getClassLoader(),
            new Class<?>[]{IntProvider.class},
            new AbstractInvocationHandler() {
                protected Object handleInvocation(
                        Object proxy, Method m, Object[] args
                ) throws Throwable {
                    if (m.isDefault()) {
                        Class<?> dc = m.getDeclaringClass();
                        return MethodHandles.lookup()
                                            .in(dc)
                                            .unreflectSpecial(m, dc)
                                            .bindTo(proxy)
                                            .invokeWithArguments(args);
                    } else if (m.getName().equals("getInt")) {
                        return n;
                    } else {
                        throw new UnsupportedOperationException();
                    }
                }
            }
    );
    assertThat(ip.getIntTimesTwo(), is(n * 2));
}

It looks to me like this should work, and frankly I don’t understand why it doesn’t, but it in some situations it doesn’t, because the MethodHandle thinks the method access level is wrong, and Method.setAccessible(true) also doesn’t help there.

Fortunately I came across this blog post by ZeroTurnaround, which provided the solution. The MethodHandles class actually doesn’t provide a factory method with the required access levels, so we need to invoke a constructor using reflection:

@Test
public void testGetIntTimesTwoWithMethodHandleWithReflection(){
    final int n = 4;
    IntProvider ip = (IntProvider) Proxy.newProxyInstance(
            IntProvider.class.getClassLoader(),
            new Class<?>[]{IntProvider.class},
            new AbstractInvocationHandler(){
                protected Object handleInvocation(
                        Object proxy, Method m, Object[] args
                ) throws Throwable {
                    if(m.isDefault()){
                        Class<?> dc = m.getDeclaringClass();
                        Constructor<Lookup> c =
                          Lookup.class // MethodHandles.Lookup
                            .getDeclaredConstructor(Class.class, int.class);
                        c.setAccessible(true);
                        return c.newInstance(dc, Lookup.PRIVATE)
                                .unreflectSpecial(m, dc)
                                .bindTo(proxy)
                                .invokeWithArguments(args);

                    }
                    else if(m.getName().equals("getInt")) return n;
                    else throw new UnsupportedOperationException();
                }
            }
    );
    assertThat(ip.getIntTimesTwo(), is(n * 2));
}

This version does work, but it’s an awful test. Waaaay to technical and involved. So let’s abstract the technical stuff away to a library method:

public final class ProxyHelper {


    /**
     * Create a proxy of the supplied interface, using the supplied
     * {@link InvocationHandler}, which will be wrapped
     * inside a {@link DefaultRespectingInvocationHandler}.
     * In effect this means that standard methods like equals /
     * hashcode / toString will be handled with sane defaults and
     * {@code default} methods will be called directly.
     * All other methods will be handled by the supplied
     * {@link InvocationHandler}.
     */
    public static <T> T mockNonDefaultMethods(
      Class<T> type, InvocationHandler h) {
        requireNonNull(type, "Type required");
        checkArgument(type.isInterface(),
          "Expected interface type but got %s", type.getName());
        requireNonNull(h, "Handler required");

        @SuppressWarnings("unchecked")
        T proxy = (T) newProxyInstance(
                          type.getClassLoader(),
                          new Class<?>[]{type},
                          wrapWithDefaults(h)
                  );

        return proxy;
    }

    private static InvocationHandler wrapWithDefaults(
      InvocationHandler inner) {

            return new DefaultRespectingInvocationHandler(inner);
    }


    private static class DefaultRespectingInvocationHandler
                 extends AbstractInvocationHandler {

        private final InvocationHandler inner;

        private DefaultRespectingInvocationHandler(
          InvocationHandler inner) {
            this.inner = inner;
        }

        @Override
        protected final Object handleInvocation(
          Object proxy, Method m, Object[] args) throws Throwable {
            if (m.isDefault()) {
                final Class<?> dc = m.getDeclaringClass();
                Constructor<Lookup> c = 
                    Lookup.class
                          .getDeclaredConstructor(
                                 Class.class, int.class);
                c.setAccessible(true);
                return c.newInstance(dc, PRIVATE)
                        .unreflectSpecial(m, dc)
                        .bindTo(proxy)
                        .invokeWithArguments(args);
            }
            return inner.invoke(proxy, m, args);
        }

    }
}

We’re using the decorator pattern here, wrapping the supplied InvocationHandler with one that takes care of both default and boilerplate methods, leaving only the “real” logic to the supplied handler.

And now we can rewrite our test in this significantly cleaner way:

@Test
public void testGetIntTimesTwoWithFactoryMethod() {
    final int n = 4;
    IntProvider ip = ProxyHelper.mockNonDefaultMethods(
      IntProvider.class, (proxy, method, args) -> {
        if (method.getName().equals("getInt")) {
            return n;
        }
        throw new UnsupportedOperationException();
    });
    assertThat(ip.getIntTimesTwo(), is(n * 2));
}

Obviously, all of this only makes sense if you have a large code base with several default methods you need to test like this. And also obviously, the Mockito way above is less painful!

Advertisements
This entry was posted in Uncategorized. Bookmark the permalink.

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s