4

How would I mock methods that accept a lambda using Mockito so that I am able to control which arguments are passed into the callback? I am specifically trying to mock the JDBI method useExtension which is used like this:

jdbi.useExtension(OrgUnitDao.class, dao -> {
    // Skip if already loaded
    // Skip if already loaded
    if (dao.orgUnitsAreLoaded()) {

I would like to substitute the dao object passed back into the callback so that I could control the branching using the return value of dao.orgUnitsAreLoaded().

The signature looks like this

public <E,X extends Exception> void useExtension(Class<E> extensionType,
                                             ExtensionConsumer<E,X> callback)
                                      throws NoSuchExtensionException,
                                             X extends Exception
oligofren
  • 20,744
  • 16
  • 93
  • 180
  • Can't we just mock the interface `ExtensionConsumer`? – grape_mao Jun 26 '18 at 11:46
  • That wouldn't be right, would it? That is what the users of the method pass in, and I don't want to mock the callback (it contains the logic I want to test). I want to control the passed in parameter of type `E` _to_ the callback. So I first need to match the arguments, then I can invoke the callback with the `mock(OrgUnitDao.class)` and `when(mock.orgUnitsAreLoaded())` – oligofren Jun 26 '18 at 11:58
  • Are you trying to test your callback, if so it has nothing to do with `useExtension` method, and maybe you'll need to refactor it into a named method. – grape_mao Jun 26 '18 at 13:25
  • That's not an unreasonable suggestion, but the lambda closes around lots of variables in the enclosing scope. Refactoring means passing around a lot of state I'd rather not deal with. I am half-way there, though ... – oligofren Jun 26 '18 at 13:51
  • lambda function or anonymous class are meant to be short, if you need to test, then probably they are too complicated. – grape_mao Jun 26 '18 at 13:56
  • You are right, @grape_mao, and I have extracted it now. Still, I think this is a recurring theme in Java 8+ projects and that in itself merits a good answer. That it shouldn't be done doesn't mean we shouldn't know how it could be done :) – oligofren Jun 26 '18 at 14:03
  • I was going to suggest using a captor as in [my answer here](https://stackoverflow.com/a/13618648/1426891), but you've seen that—you kindly updated its link last month. (Thank you!) Do you think it would be appropriate to suggest as an additional answer here, or should I infer from this question that you're solving a different problem than that one? – Jeff Bowman Jun 26 '18 at 17:57
  • @JeffBowman, I think that would make a good addition! I forgot all about your answer and numerous searches only brought up irrelevant hits on SO. Not that one. – oligofren Jun 26 '18 at 18:14

2 Answers2

0

This is the full answer to my question. It's simplified down to the very basics of how to do the stubbing and so doesn't reflect the production code I am to test, but it shows exactly the mechanics needed to do it.

final Jdbi jdbi = mock(Jdbi.class);
doAnswer(invocation -> {

    System.out.println("this is the doAnswer lambda - just setting up the answer and the mocks");

    final Class<OrgUnitDao> daoClass = invocation.getArgument(0);
    final ExtensionConsumer callback = invocation.getArgument(1);
    final OrgUnitDao mock1 = mock(daoClass);

    when(mock1.orgUnitsAreLoaded()).thenReturn(false);

    // call the actual callback method
    callback.useExtension(mock1);

    return null;

}).when(jdbi).useExtension(eq(OrgUnitDao.class), any());

// This is the method call I am to test
// Regard this as hidden away in some outer method in
// the System-Under-Test, but that I have been able
// to inject all its dependencies
jdbi.useExtension(OrgUnitDao.class, new Foo());

/// Further down, outside of the method

// Only replaced the lambda with this to get toString() for debugging ...
class Foo implements ExtensionConsumer<OrgUnitDao, RuntimeException> {
    @Override
    public void useExtension(OrgUnitDao orgUnitDao) throws RuntimeException {
        System.out.println("A real method call, now using the passed in mocked dao:" + orgUnitDao.orgUnitsAreLoaded());
    }

    @Override
    public String toString() {
        return "OrgUnitDao class";
    }
}
oligofren
  • 20,744
  • 16
  • 93
  • 180
  • Here is the thing I don't understand, why not invoke the mocked DAO directly. What are you trying to test with `Foo`. – grape_mao Jun 26 '18 at 14:16
  • That was just instructive of the mechanics of _how to do the actual stubbing_. My answer does not show an actual test of anything except Mockito's API. The`useExtension` method (of the `Foo` class) is what I am really testing (it contains the snippet mentioned in the question) - althought _why_ is not actually relevant for the question. The question is not about proper design or writing good tests. It's about syntax and the Mockito API. – oligofren Jun 26 '18 at 14:52
  • 1
    Ok, I thought you were looking for a way to catch the lambda and invoke the argument without duplicate the implementation code. – grape_mao Jun 26 '18 at 15:50
0

To parallel the conversation on the question "Calling callbacks with Mockito", your lambda might be called synchronously during the execution of your method-under-test, or it might be called later based on some external factor or interaction. Like Dawood's answer there, your answer here using a Mockito Answer will work, and is the only way to go if you are looking for the synchronous style (where mockJdbi calls your lambda immediately before methodUnderTest returns). If your lambdas are going to be called asynchronously, or if your system tolerates you calling the lambda asynchronously, you might want to test the state after your method-under-test returns but before you interact with the lambda.

// MockitoJUnitRunner, MockitoRule, or MockitoAnnotations.initMocks populate these.
// Especially useful for the ArgumentCaptor's generic arguments.
@Mock Jdbi mockJdbi;
@Mock OrgUnitDao mockOrgUnitDao;
@Captor ArgumentCaptor<ExtensionConsumer<OrgUnitDao, RuntimeException>>
    extensionConsumerCaptor;

@Test public void yourTest() throws Exception {
  // No stubbing needed! Just create the system under test.
  YourSystemUnderTest systemUnderTest = new YourSystemUnderTest(mockJdbi);

  // Call the method under test, which presumably calls useExtension(...).
  systemUnderTest.methodUnderTest();

  // Assert anything that should be true before the lambda is called.
  assertFalse(systemUnderTest.getSomeState());

  // Confirm that useExtension was called, and simultaneously acquire the lambda.
  // ArgumentCaptor.capture() is a matcher, so every argument requires a matcher like eq.
  verify(mockJdbi).useExtension(eq(OrgUnitDao.class), extensionConsumerCaptor.capture());

  // Prepare the mock DAO and call the lambda.
  when(mockDao.getFoo()).thenReturn("bar");
  extensionConsumerCaptor.getValue().useExtension(mockDao);

  // Assert anything that should be true after the lambda is called.
  assertTrue(systemUnderTest.getSomeState());
}

Though lambdas reduce the boilerplate previously associated with anonymous inner classes, you may also prefer using the Captor style because it saves you from creating lengthy Answer implementations and hiding your test assertions or Mockito verifications in them. This is especially tempting if your project prefers BDD-style mocks with clear "given-when-then" structure (though my example more-closely resembles "given-when-then-when-then").

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251