user2077221
user2077221

Reputation: 934

Mockito; Mock a method that calls a lambda, and verify another mock called by that lambda

My question, which was similar to this:

Using mockito; is it possible to mock a method that takes a lambda as a parameter and assert variables captured by the lambda?

But different enough that I still had take a while to figure it out was:

How do I verify that a method called by a mock that was used inside a lambda that was passed to a method of another mock object?

This may seem convoluted, but it happens a lot with Java 8 libraries like JDBI, for example, you have a JDBI object:

JDBI MyDBConnection

That you should mock. And then that is used with the withHandle method to pass a lambda implementing the HandleCallback<R,X> type:

//code I'm testing. I implement the lambda, and want to verify it
//calls the correct method in dao provided by JDBI.
MyDBConnection.withHandle(
    (handle) -> { ... handle.attach(SomeDao.class).findSomethingInDB(args) .. }

Which is the recommended way to do this.

So I want to verify that findSomethingInDB(eq(args)) is called.

Like I said this was similar, but different enough, that, I at least, will find this answer valuable at some future point, when I forget how to do this. So the original 3rd party library method that invokes my lambda is processed similar to the answer given in the question referenced above, but with some tweaks:

when(JDBIMock.withHandle(any())).then(
  //Answer<Void> lambda
  invocationOnMock -> {
     Object[] args = invocationOnMock.getArguments();
     assertEquals(1, args.length);
     //the interface def for the callback passed to JDBI
     HandleCallback lambda = (HandleCallback) args[0];
     when(mockHandle.attach(SomeDao.class)).thenReturn(mockDao);
     //this actually invokes my lambda, which implements the JDBI interface, with a mock argument
     lambda.withHandle(mockHandle);
     //bingo!
     verify(mockDao).findSomethingInDB(eq(args));
  }
)

Upvotes: 1

Views: 1827

Answers (3)

Vaent
Vaent

Reputation: 1

This was a very helpful starting point for me trying to mock a useTransaction call on a JDBI instance, which is more like useHandle than withHandle in that it has void return type and is incompatible with the OP's example.

For completeness in case others come looking:

Jdbi mockDbi = mock(Jdbi.class);
Handle mockHandle = mock(Handle.class);
MyDAO mockDao = mock(MyDAO.class);
when(mockHandle.attach(MyDAO.class)).thenReturn(mockDao));

// void return requires doAnswer().when() pattern instead of when().then()
doAnswer(invocation -> {
    // HandleConsumer replaces HandleCallback
    HandleConsumer<?> callback = invocation.getArgument(0);
    callback.useHandle(mockHandle);
    // any further logic and assertions for your use case
    return null; // Answer must return something even if the lambda doesn't
}).when(mockDbi).useTransaction(any());

Upvotes: 0

Chris R
Chris R

Reputation: 2564

I am trying to do something very similar with verifying the arguments passed to another mock from withHandle on a mock JDBI call in a test.

The answer you give in the question pointed me in the right direction but gives me the error message:

The method then(Answer<?>) in the type OngoingStubbing<Object> is not applicable for the arguments ((<no type> invocationOnMock) -> {})

Instead I had to use a new org.mockito.stubbing.Answer passed to the then, similar to in the other question you linked to.

In your example this would be something like:

when(JDBIMock.withHandle(any())).then(
  //Answer<Void> lambda
  new Answer<Void>() {
    @Override
    public Void answer(InvocationOnMock invocation) throws Throwable {
      Object[] args = invocation.getArguments();
      assertEquals(1, args.length);
      //the interface def for the callback passed to JDBI
      HandleCallback lambda = (HandleCallback) args[0];
      when(mockHandle.attach(SomeDao.class)).thenReturn(mockDao);
      //this actually invokes my lambda, which implements the JDBI interface, with a mock argument
      lambda.withHandle(mockHandle);
      //bingo!
      verify(mockDao).findSomethingInDB(eq(args));

      return null; // to match the Void type
    }
  }
)

In my case I was expecting a result list from withHandle so I had to change the Answer type, and return type of answer to match and return a dummy list instead of Void. (The actual results returned didn't matter in this test, only that the expected arguments were passed to my subsequent mock object).

I also moved the verify call outside of the Answer into the main body of my test so it was clearer this was the expectation of the test, not part of the mocking setup.

Upvotes: 0

user2077221
user2077221

Reputation: 934

See the question, it should be answered sufficiently above ;)

Upvotes: 0

Related Questions