Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spying a lambda with mockito

I have encountered an interesting issue while writing an unit test which involved mocking a lambda.

@Test
public void spyingLambda() {
    final Supplier<String> spy = Mockito.spy((Supplier) () -> "1");
    spy.get();
}

Running this test fails with the following error:

Mockito cannot mock/spy because : - final class

One workaround for the above issue is replacing the lambda with anonymous implementation:

@Test
public void spyingAnonymousImplementation() {
    final Supplier<String> spy = Mockito.spy(new Supplier<String>() {
        @Override
        public String get() {
            return "1";
        }
    });
    spy.get();
}

Though both tests are exactly the same (the IDE suggest even replacing the anonymous implementation with lambda), the second test doesn't fail.

I was wondering if this is a known issue which could be fixed in mockito or are there any other workarounds.

like image 246
Alex Objelean Avatar asked Jan 23 '19 13:01

Alex Objelean


3 Answers

Another way of dealing with this issue is the following:

/**
 * This method overcomes the issue with the original Mockito.spy when passing a lambda which fails with an error
 * saying that the passed class is final.
 */
@SuppressWarnings("unchecked")
static <T, P extends T> P spyLambda(Class<T> lambdaType, P lambda) {
    return (P) mock(lambdaType, delegatesTo(lambda));
}

Which allows spying the lambda by changing the first method as following:

@Test
void spyingLambda() {
    Supplier<String> spy = spyLambda(Supplier.class, () -> "1");
    spy.get();
}

Hopefully the above examples might help others who encounter the same issue.

like image 148
Alex Objelean Avatar answered Nov 18 '22 20:11

Alex Objelean


Just for the reference, to improve @alex's answer, you can also do

public static <T> T spyLambda(final T lambda) {
    Class<?>[] interfaces = lambda.getClass().getInterfaces();
    MatcherAssert.assertThat(interfaces, IsArrayWithSize.arrayWithSize(1));
    return Mockito.mock((Class<T>) interfaces[0], delegatesTo(lambda));
}

and then simply spy it without specifying the type (e.g., Supplier.class)

Callable<Integer> callable = spyLambda(() -> {
    return 42;
});
Supplier<Integer> supplier = spyLambda(() -> 42);
Runnable runnable = spyLambda(() -> System.out.println("42"));

like image 29
Stepan Vavra Avatar answered Nov 18 '22 20:11

Stepan Vavra


You can allow final class mocking. Create file src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker containing

mock-maker-inline

https://www.baeldung.com/mockito-final#configure-mocktio

like image 2
Marthym Avatar answered Nov 18 '22 20:11

Marthym