Hot questions for Using Mockito in lambda

Question:

I want to test the following method:

    public void dispatchMessage(MessageHandler handler, String argument1, String argument2, Long argument3) {

    handler.registerMessage(() -> {
        dispatcher.dispatch(argument1,
                argument2,
                argument3);
    });

}

Where MessageHandler is a helper class which will accept a Functional Interface implementation in the form a lambda, and store it for later execution.

Is there a way to verify with mockito that the dispatchMessage method of the mocked MessageHandler has been called with the specific lambda expression:

Meaning, can I write such a test:

        @Test
public void testDispatchMessage_Success() throws Exception {

    myMessageDispatcher.dispatchMessage(handler, "activityId", "ctxId", 1l, );

    verify(handler, times(1)).dispatchMessage(() -> {
        dispatcher
            .dispatch("activityId", "ctxId", 1l,);
    });

}

}

This test will result in assertion error: Argument(s) are different! Wanted:

......Tests$$Lambda$28/379645464@48f278eb

Actual invocation has different arguments:

..........Lambda$27/482052083@2f217633

which makes sense since mockito tries to compare two different implementations of the functional interface, which have a different hash code.

So is there some other way to verify that the method dispatchMessage() has been called with a lambda that returns void and has a body method of dispatcher.dispatch("activityId", "ctxId", 1l,); ?


Answer:

Yes, you can. The trick here is that you have to get to the instance of the lambda that is passed to the registerMessage and then execute that expression and then you can verify the result.

For the purpose of a meaningful example I created this Handler class that contains the dispatchMessage that you want to test:

public class Handler {

    private Dispatcher dispatcher = new Dispatcher();

    public void dispatchMessage(MessageHandler handler, String argument1, String argument2, Long argument3) {

        handler.registerMessage(() -> {
            dispatcher.dispatch(argument1,
                    argument2,
                    argument3);
        });

    }

    interface MessageHandler {
        void registerMessage(Runnable run);
    }

    static class Dispatcher {
        void dispatch(String a, String b, long c){
            // Do dispatch
        }
    }
}

What you have to remember is that a lambda expression is just a short hand form to pass a function to a method. In this example the function is the run method of a Runnable. Therefore the method registerMessage of the interface for MessageHandler takes a Runnable as it's argument. I also included an implementation for the Dispatcher, which is called from within registerMessage. The test for this looks like this:

@RunWith(MockitoJUnitRunner.class)
public class HandlerTest {
    @Mock
    private Dispatcher dispatcher;
    @InjectMocks
    private Handler classUnderTest;
    @Captor
    private ArgumentCaptor<Runnable> registerMessageLambdaCaptor;

    @Test
    public void shouldCallDispatchMethod() {
        final String a = "foo";
        final String b = "bar";
        final long c = 42L;

        MessageHandler handler = mock(MessageHandler.class);

        classUnderTest.dispatchMessage(handler, a, b, c);

        verify(handler).registerMessage(registerMessageLambdaCaptor.capture());

        Runnable lambda = registerMessageLambdaCaptor.getValue();

        lambda.run();

        verify(dispatcher).dispatch(a, b, c);
    }
}

There is an ArgumentCaptor for the lambda expression which we use in the first verification of the registerMessage. After that verification we can retrieve the lambda expression from the captor. The type of the lambda expression is Runnable, as defined in the MessageHandler interface. Hence we can call the run method on it and then verify that the dispatch method on the Dispatcher was called with all the appropriate arguments.

Question:

I'm trying to mock a collaborator, named Worker and capture arguments of its method execute which runs in different thread. However, the method itself has method references as arguments: childService::listClients and childService::refreshObjects. When I assert method references with captured arguments, I get different lambda objects.

Is there a way to reach and assert them in proper way?

The class being tested:

public class ParentService {
    private ChildService childService;
    private Worker worker;
    ...
    public void doAction() {
        worker.execute(
                childService::listClients,
                childService::refreshObjects
        );
    }
}

The test:

@Test
public void shouldUseChildService() {
    ArgumentCaptor<Callable> callableCaptor = ArgumentCaptor.forClass(Callable.class);
    ArgumentCaptor<Consumer> consumerCaptor = ArgumentCaptor.forClass(Consumer.class);

    parentService.doAction();

    verify(worker).execute(callableCaptor.capture(), consumerCaptor.capture());
    assertEquals((Callable) childService::listClients, callableCaptor.getValue());
    assertEquals((Consumer) childService::refreshObjects, consumerCaptor.getValue());
}

Assertion error:

    java.lang.AssertionError: 
    Expected :org.app.services.ParentServiceTest$$Lambda$4/990416209@1786dec2
    Actual   :org.app.services.ParentServiceTest$$Lambda$1/1340328248@74650e52
at org.junit.Assert.failNotEquals(Assert.java:743)
    at org.junit.Assert.assertEquals(Assert.java:118)
    at org.junit.Assert.assertEquals(Assert.java:144)
    at org.app.services.ParentServiceTest.shouldUseChildService(ParenServiceTest.java:66)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.mockito.internal.runners.JUnit45AndHigherRunnerImpl.run(JUnit45AndHigherRunnerImpl.java:37)
    at org.mockito.runners.MockitoJUnitRunner.run(MockitoJUnitRunner.java:62)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

Answer:

First Mock your Worker with Mockito (just as you did). Also mock your ChildService. Then:

@Test
public void shouldUseChildService() {
  ArgumentCaptor<Callable> callableCaptor = ArgumentCaptor.forClass(Callable.class);
  ArgumentCaptor<Consumer> consumerCaptor = ArgumentCaptor.forClass(Consumer.class);

  parentService.doAction();

  verify(worker).execute(callableCaptor.capture(), consumerCaptor.capture());
  callableCaptor.getValue().call(); //this will execute whatever was captured
  consumerCaptor.getValue().accept(null);//this will execute whatever was captured

  // now verify that childService::listClients and childService::refreshObjects have been called
}

Question:

I am trying to test a method that takes a Consumer function, and I want to verify with Mockito that my lambda expression is called exactly once. What I'm using now is the kind of clunky way of using a flag on a final, single-element array:

final boolean[] handlerExecuted = {false};
instance.conditionalRun(item -> {
  handlerExecuted[0] = true;
  item.foo();
});

Assert.assertTrue(
    "Handler should be executed.",
    handlerExecuted[0]);

It seems like there should be a better way (with a Mockito spy, perhaps) to verify that this lambda expression was called exactly once.


Answer:

Some of the other answers offer alternatives to doing exactly what I want here, but this is doable by Spying the Consumer class itself and having the spy call the method you really want to execute. A helper method for wrapping the lambda to create the spy helps here:

/** Get a spied version of the given Consumer. */
private Consumer<Item> itemHandlerSpy(Consumer<Item> itemHandler) {
  // Create a spy of the Consumer functional interface itself.
  @SuppressWarnings("unchecked")
  Consumer<Item> spy = (Consumer<Item>) Mockito.spy(Consumer.class);
  // Tell the spy to run the given consumer when the Consumer is given something to consume. 
  Mockito.doAnswer(it -> {
    // Get the first (and only) argument passed to the Consumer.
    Item item = (Item) it.getArguments()[0];
    // Pass it to the real consumer so it gets processed.
    itemHandler.accept(item);
    return null;
  }).when(spy).accept(Mockito.any(Item.class));
  return spy;
}

And then the test method becomes very straightforward:

Consumer<Item> itemHandler = itemHandlerSpy(Item::foo);
instance.conditionalRun(itemHandler);
// This verifies conditionalRun called the Consumer exactly once.
Mockito.verify(itemHandler).accept(Mockito.any(Item.class));

Question:

I have a kotlin Android app. There is a function that loads compositions from the backend and returns them to a callback:

getCompositons(callback: (Array<Composition>) -> Unit)

How can I mock the callback using mockito. So that I then can do something like this:

var callback = //mockito mock
getCompositons(callback) 
verify(callback, timeout(10000)).apply()

I read that lambda are matched to the java type function and therefore I assume apply could be the method invoked. Maybe I could mock a function and use that? But the Kotlin function interface only seems to have one return type, no parameters. java.util.Function says unresolved reference function.

Any help appreciated.


Answer:

This is really no different to mocking any other type:

val callback = mock<(Array<Composition>) -> Unit>()

getCompositons(callback)

verify(callback)(any())  // Or verify(callback).invoke(any()) to be explicit

(In case you weren't aware of them, I'm using the mockito-kotlin bindings here.)

Question:

I want to mock a query provided on my repository like this:

@Test
public void GetByEmailSuccessful() {
    // setup mocks
    Mockito.when(this.personRepo.findAll()
            .stream()
            .filter(p -> (p.getEmail().equals(Mockito.any(String.class))))
            .findFirst()
            .get())
            .thenReturn(this.personOut);
    Mockito.when(this.communityUserRepo.findOne(this.communityUserId))
            .thenReturn(this.communityUserOut);
...

My @Before method looks like this:

@Before
public void initializeMocks() throws Exception {
    // prepare test data.
    this.PrepareTestData();

    // init mocked repos.
    this.personRepo = Mockito.mock(IPersonRepository.class);
    this.communityUserRepo = Mockito.mock(ICommunityUserRepository.class);
    this.userProfileRepo = Mockito.mock(IUserProfileRepository.class);
}

Sadly when I run the test I receive the error:

java.util.NoSuchElementException: No value present

When I double-click the error it points at the .get() method of the first lambda.

Have any of you successfully mocked a lambda expression and know how I can solve my problem?


Answer:

There's no need to mock such deep calls. Simply mock personRepo.findAll() and let the Streaming API work as normal:

Person person1 = ...
Person person2 = ...
Person person3 = ...
List<Person> people = Arrays.asList(person1, person2, ...);
when(personRepo.findAll()).thenReturn(people);

And then instead of

.filter(p -> (p.getEmail().equals(Mockito.any(String.class))))

just set/mock email on your Person objects to be the expected value.

Alternatively, consider implementing PersonRepo.findByEmail.

Question:

I am using SpringAMQP where I am testing producer method (basically AMQP template) which is like this.

public void send(Message message, Throwable error, String queue, String routingKey) {

    this.amqpTemplate.convertAndSend(
        RabbitConfiguration.ERROR_EXCHANGE,
        RabbitConfiguration.ERROR_ROUTING_KEY,
        message,
        messageMetaData -> {

            messageMetaData.getMessageProperties().getHeaders().put("x-death-reason", error.getMessage());

            return messageMetaData;
        }
    );
}

I am testing this code with following

import static org.hamcrest.Matchers.any;
....
@Test
public void will_create_error_message_if_incorrect_payload_is_given() {

    AmqpTemplate amqpTemplate = mock(AmqpTemplate.class);
    Throwable throwable = mock(Throwable.class);
    when(throwable.getMessage()).thenReturn("first");
    when(throwable.getStackTrace()).thenReturn(null);

    ErrorMessageProducer errorMessageProducer = new ErrorMessageProducer(amqpTemplate);

    Message message = MessageBuilder.withBody("test".getBytes()).build();

    verify(amqpTemplate).convertAndSend(
        eq(RabbitConfiguration.ERROR_EXCHANGE),
        eq(RabbitConfiguration.ERROR_ROUTING_KEY),
        any(Message.class),
        Mockito.any()
    );
}

But I am getting Invalid use of argument matchers! 4 matchers expected, 3 recorded. Is there any way I can test with Lambda or ignore Lambda altogether.


Answer:

The problem is because you are using wrong any().

verify(amqpTemplate).convertAndSend(
    eq(RabbitConfiguration.ERROR_EXCHANGE),
    eq(RabbitConfiguration.ERROR_ROUTING_KEY),
    any(Message.class),
    Mockito.any()
);

Here your 3rd argument using any from org.hamcrest.Matchers.any, however 4th argument uses right Mockito.any(). So 3rd argument isn't detected as a matcher, but is threated like a usual argument.

To check your lambda you should probably use ArgumentCaptor.

ArgumentCaptor<Runnable> argument = ArgumentCaptor.forClass(Runnable.class);
verify(mock).doSomething(any(), argument.capture());
argument.getValue().run();
...verify that lambda called your services...

You can change Runnable to any type of function your lambda actually represents: i.e. Function/Callable.

Question:

I want to mock the following method. But I don't find any Mockito.Matchers for the second parameter which uses Java.util.Function.

public List<String> convertStringtoInt(List<Integer> intList,Function<Integer, String> intToStringExpression) {
        return intList.stream()
                .map(intToStringExpression)
                .collect(Collectors.toList());
    }

I am looking for something like this:

Mockito.when(convertStringtoInt(Matchers.anyList(),Matchers.anyFunction()).thenReturn(myMockedList)

Answer:

If you only want to mock the Function argument then either of the following would work:

Mockito.when(convertStringtoInt(Matchers.anyList(), Mockito.any(Function.class))).thenReturn(myMockedList);

Mockito.when(convertStringtoInt(Matchers.anyList(), Mockito.<Function>anyObject())).thenReturn(myMockedList);

Given a class, Foo, which contains the method: public List<String> convertStringtoInt(List<Integer> intList,Function<Integer, String> intToStringExpression) the following test case passes:

@Test
public void test_withMatcher() {
    Foo foo = Mockito.mock(Foo.class);

    List<String> myMockedList = Lists.newArrayList("a", "b", "c");

    Mockito.when(foo.convertStringtoInt(Matchers.anyList(), Mockito.<Function>anyObject())).thenReturn(myMockedList);

    List<String> actual = foo.convertStringtoInt(Lists.newArrayList(1), new Function<Integer, String>() {
        @Override
        public String apply(Integer integer) {
            return null;
        }
    });

    assertEquals(myMockedList, actual);
}

Note: if you actually want to invoke and control the behaviour of the Function parameter then I think you'd need to look at thenAnswer().

Question:

The class I'm unit testing takes a dependency and calls a function on that dependency. That function takes a complex object as its argument and produces a result. Now, I would like to mock the dependency and have it return something based on the argument passed in. A simplified, working version is given below.

Can I use Java 8 lambda expressions in the when method to eliminate the ArgHasNext class? Something like the commented code below (which does not compile).

class ArgHasNext implements ArgumentMatcher<Arg> {
    public boolean matches(Arg arg) {
        return arg.hasNext();
    }

    @Override
    public boolean matches(Object o) {
        return matches((Arg)o);
    }
}

@RunWith(MockitoJUnitRunner.class)
public class ArgumentMatcherTest {

    @Mock
    private Dependency dep = mock(Dependency.class);

    @Test
    public void test() {

        when(dep.func(argThat(new ArgHasNext()))).thenReturn(true);
        // when(dep.func(argThat((Arg a) -> a.hasNext()))).thenReturn(true);
        // when(dep.func(argThat((Arg a) -> !a.hasNext()))).thenReturn(false);
        Sut sut = new Sut(dep);
        assertEquals(sut.method(new Arg(true)), "True");
        assertEquals(sut.method(new Arg(false)), "False");
    }
}

class Arg {
    private boolean hasNext;

    public Arg(boolean hasNext) {
        this.hasNext = hasNext;
    }

    public boolean hasNext() {
        return this.hasNext;
    }
}

class Sut {
    private Dependency dep;

    public Sut(Dependency dep) {
        this.dep = dep;
    }

    public String method(Arg arg) {

        if (dep.func(arg)) {
            return "True";
        }
        else {
            return "False";
        }
    }

}

class Dependency {
    public boolean func(Arg arg) {
        if (arg.hasNext()) {
            return true;
        }
        return false;
    }
}

I'm using Mockito-core version 2.0.54-beta.

EDIT Okay, maybe I over-simplified the example. In the real case, the dependency func method returns paged query results that are processed in SUT before the tested method returns. Depending on Arg, I want the dependency func to return page 1 results the first time it's called and page 2 results the second time. I can only do this when I can have the mock return different values based on an argument passed to the function call. It this possible using lambdas in Mockito?


Answer:

Now, I would like to mock the dependency and have it return something based on the argument passed in.

You should not do that in the first place. By any chance let your mocks return well defined constant values.

The reason is that you should keep your test code as simple as possible to reduce the risk of having your tests failing because the test code is wrong.


The solution to your problem might be mockitos Answer interface:

doAnswer(new Answer<YourReturnType>(){ 
    public YourReturnType answer(InvocationOnMock invocation) {
       YourParameterType parameter = (YourParameterType)invocation.getArguments()[0];
       // calculate your return value
       return yourCalculatedReturnValue;
    }
}).when(yourMock).theMethod(any(YourParameterType.class));

To be clear, my mock is returning a constant value. The mock is called multiple times and I want it to return a different value the second time. – MvdD

You schould have written this in your question. The Solution is as simple as it sounds:

doReturn(VALUE_FOR_FIRST_CALL).
   thenReturn(VALUE_FOR_SECOND_CALL).
   thenReturn(VALUE_FOR_ANY_FURTHER_CALL).
   when(mock).theMethod();

Or if you prefer being less chatty:

doReturn(VALUE_FOR_FIRST_CALL, VALUE_FOR_SECOND_CALL, VALUE_FOR_ANY_FURTHER_CALL).
   when(mock).theMethod();

Question:

I am writing a unit test for functionA(). My problem is that I don't know if there exists a way to trigger the lambda's apply() which is in functionC() of DependentClass. The lambda lambdaFunction is a functional interface that stores call to functionB().

SystemUnderTest:

public class SystemUnderTest {

    private DependentClass dependentClass;

    public void functionA() {

        LambdaFunction lambdaFunction = (Foo foo, 
                                         Bar bar) -> {

            functionB(foo, bar); // needs to execute by triggering apply() on lambdaFunction. i don't know of a way to explicitly call functionB() without triggering it.

        };

        dependentClass.functionC(lambdaFunction); // needs to be called so that apply() is triggered, so should i be mocking this call?
    }

    private void functionB(Foo foo, 
                           Bar bar) {
            .
            .
            .
    }
}

DependentClass that needs to be mocked out:

public class DependentClass {

    public void functionC(LambdaFunction lambdaFunction) {

        lambdaFunction.apply(foo, bar); // it would be nice to invoke apply() so that functionB() executes.

    }
}

Functional interface whose implementation stores the to call to functionB():

@FunctionalInterface
public interface LambdaFunction {

    void apply(Foo foo, 
               Bar bar);
}

JUnit raw test:

public class ClassUnderTestJUnitTest {

    @Mock 
    private DependentClass dependentClassMock;

    @Mock
    private LambdaFunction lambdaFunctionMock;

    @Test
    public void test_functionA() {

        Mockito.doNothing().when(dependenClassMock).functionC(lambdaFunctionMock);

        SystemUnderTest systemUnderTest = new SystemUnderTest(dependenClassMock);

        systemUnderTest.functionA();
    }
}

However, as you can see I don't know if there exists a way such that I can invoke functionB(). Ideally, it'd be very helpful if I could make a real method call (I don't know if this is a good practice though) for dependentClass.functionC(lambdaFunction) which would then trigger lambdaFunction.apply(foo, bar) resulting in calling functionB(foo, bar). My apologies if I didn't explain the problem statement well. I'd appreciate any help or hints on how to achieve writing the unit test/s for this scenario.


Answer:

You have 2 choices to verify the LambdaFunction whether is called or not.

State Based Verification

Don't inject a Test Double but a real implementation of the DependentClass into the SystemUnderTest, and then verify the affected states after call functionA in SystemUnderTest. for example:

@Test
public void test_functionA() {
    //             use a real collaborator if possible ---v
    SystemUnderTest systemUnderTest = new SystemUnderTest(new DependentClass());
    systemUnderTest.functionA();

    // assert the SUT affected states
    assertEquals(expectedState, systemUnderTest.affectedState);
}
Behavior Based Verification

Behavior based verification is testing the communication between SUT and its collaborators, so you can't test it by a single test. Indeed, you need 2 separated tests for that. one is test the communication, another is test the collaborator is whether fulfill its contract.

@RunWith(MockitoJUnitRunner.class)
public class ClassUnderTestJUnitTest {

    @Mock
    private DependentClass dependentClassMock;


    @Test
    public void test_functionA() {
        SystemUnderTest systemUnderTest = new SystemUnderTest(dependentClassMock);

        systemUnderTest.functionA();

        //     v--- verify its communication
        verify(dependentClassMock, only()).functionC(any(LambdaFunction.class));
    }
}

@RunWith(MockitoJUnitRunner.class)
public class DependentClassTest {
    private final DependentClass dependentClass = new DependentClass();
    private @Mock LambdaFunction function;

    @Test
    public void test_functionA() {
        dependentClass.functionC(function);

        //     v--- verify it whether fulfill its contract
        verify(function, only()).apply(any(Foo.class), any(Bar.class));
    }
}

For more details, you can see martinfowler's blog: Mocks aren't Stubs.

Question:

I am currently testing a method in which I execute a runnable into a ThreadPool.

Here is the code for the method.

@Override
public void queueLogin(Connection connection, LoginPacket packet) {
    if(isRunning)
        loginPool.execute(() -> loginService.tryLogin(connection, packet));
}

Now I need to verify whether the execute method was invoked or not with the correct parameters.

Here is my test case

@Test
public void theQueueLoginShouldCallExecuteOnThreadPoolIfManagerIsRunning() {
    //Arrange
    loginManager.start();

    //Act
    loginManager.queueLogin(moqConnection, moqLoginPacket);

    //Assert
    verify(moqLoginPool).execute(() -> moqLoginService.tryLogin(moqConnection, moqLoginPacket));
}

But the test is failing saying

Argument(s) are different! Wanted:
executorService.execute(
    com.battletanks.game.server.unittests.manager.login.LoginManagerTests$$Lambda$2/1668016508@3d51f06e
);
-> at com.battletanks.game.server.unittests.manager.login.LoginManagerTests.theQueueLoginShouldCallExecuteOnThreadPoolIfManagerIsNotRunning(LoginManagerTests.java:84)
Actual invocation has different arguments:
executorService.execute(
    com.battletanks.game.server.manager.login.DefaultLoginManager$$Lambda$1/77269878@7ed7259e
);

I understand what is wrong here but I am not sure how to fix this.


Answer:

The way you have it, you're relying on these two lambdas to be equal to one another. Because they don't have an overridden equals method, and are not the same instance, you won't be able to verify that way.

Instead, capture the argument, run it, and see that it does what you want it to do.

ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.for(Runnable.class);
verify(moqLoginPool).execute(runnableCaptor.capture());

verify(moqLoginService, never()).tryLogin(any(), any());
runnableCaptor.getValue().run();
verify(moqLoginService).tryLogin(any(), any());

Question:

I need to Unit-Test a function doClassAction and inside it calls the function doStoreAction on the object store. This doStoreAction accepts two parameters, one integer and one function, like in the example below

class MyStore() {

    fun doStoreAction(num: Int, callback: (text: String) -> Unit) {
        // It does something 
        // ...
        // and when it's done it calls the callback

        return callback("some text")
    }
}

class MyClass(private val store: MyStore,
              private val objectX: MyObject) {

    fun doClassAction(num: Int) {
        store.doStoreAction(num) { callbackResult ->
            // Action (potentially asynchronous)
            // that will happen after obtaining the value via the callback

            objectX.doObjectAction(callbackResult)
        }
    }
}

I am using Mockito and so far my test has the following lines

@Test
fun doClassActionTest() {
    val mockStore = mock(MyStore::class.java)
    val mockObject = mock(MyObject::class.java)
    val class = MyClass(mockStore, mockObject)
    class.doClassAction(42)
}

But it doesn't compile because the call to the method does not specify the callback... I don't want to overwrite it since I want the test to use the original one and verify that the things inside are called properly, like:

when(objectX.doObjectAction(anyString())).doNothing()
verify(objectX, times(1)).doObjectAction(callbackResult)

Is there a way to call the method and also mock the callback response to a value I set in the tests but still have it go through the original code flow?


Answer:

After days of trying out things and reading about it, I finally found a solution that works and I'll share it here for whomever find themselves in the same pickle

@Test
fun doClassActionTest() {
    val mockStore = mock(MyStore::class.java)
    val mockObject = mock(MyObject::class.java)
    val myClass = MyClass(mockStore, mockObject)
    val input = 42
    val resultCallback = "test result"

    `when`(mockStore.doStoreAction(any(), any())).then { invocation ->
        (invocation.arguments[1] as (String) -> Unit).invoke(resultCallback)
    }

    myClass.doClassAction(input)

    verify(mockObject, only()).doObjectAction(resultCallback)
}

The caveat here is to remember to put the classes that you are mocking AND the methods called by those classes as open in Kotlin otherwise there could be false positives

P.s. I also used few inline functions extracted from the mockito-kotlin library such as:

inline fun <reified T : Any> any() = Mockito.any(T::class.java) ?: createInstance<T>()

inline fun <reified T : Any> createInstance(): T = createInstance(T::class)

fun <T : Any> createInstance(kClass: KClass<T>): T = castNull()

@Suppress("UNCHECKED_CAST")
private fun <T> castNull(): T = null as T