11

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.

user108471
  • 2,488
  • 3
  • 28
  • 41
  • 2
    I would try to have `item` as a mock/spy (and verify that `foo()` was called on it). But how to do that depends on how `item` is actually created/accessed in the `conditionalRun` method. So try to provide more context/code. – k5_ May 23 '17 at 20:19

5 Answers5

12

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));
user108471
  • 2,488
  • 3
  • 28
  • 41
  • first, up-vote. I'm happy you can solve your problem by yourself. but the test has smells and makes your test more complicated in this way. I still say that: "If you only want to testing a `Consumer` is accepted once, you can just ignoring the `item` since it is not the scope of testing in such a test, then your test is more clearer and simpler". – holi-java May 24 '17 at 10:13
1

You can verify that your method was called with any lambda expression was called like this:

    verify(someClass).myMethodThatExpectsALambda(any())

    private fun <T> any(): T {
        Mockito.any<T>()
        return null as T
    }
Blundell
  • 75,855
  • 30
  • 208
  • 233
  • I get this error: InvalidUseOfMatchersException when I use any() to mock any lambda block. Thoughts? – Rowan Gontier Feb 02 '20 at 22:43
  • 2
    Do you have multiple params/arguments. i.e. you aren't allowed some matchers and some raw values together, has to be all raw or all matches. – Blundell Feb 03 '20 at 14:16
0

Your ways can also verify that is invoked exactly once by replacing boolean[] with int[], for example:

final int[] calls = new int[1];

instance.conditionalRun(item -> {
    calls[0]++;
    item.foo();
});

Assert.assertEquals(1, calls[0]);

Note: using mockito is more complicated since you called item.foo() later.

Edit

After thinking I found that your test testing more than one behavior that result in the test is complicated, for more details you can see Why should unit tests test only one thing?, and the test can be separated at least as below since I see the intention of your test code:

@Test
void runOnceWhenConditionIsSatisfied() throws Throwable {
    final int[] calls = new int[1];

    instance.conditionalRun(item -> calls[0]++);

    Assert.assertEquals(1, calls[0]);
}

@Test
void anotherTest() throws Throwable {
    instance.conditionalRun(item -> {
        item.foo();
    });
    //... testing your SUT here 
}

AND then you can happy to replace State Verification with Behavior Verification by using mockito as below.

@Test
void runOnceWhenConditionIsSatisfied() throws Throwable {
    Consumer<T> it= Mockito.mock(Consumer.class);
    //       ^--- T is the type of item

    instance.conditionalRun(it);

    Mockito.verify(it,Mockito.only()).accept(Mockito.any());
    // using any to ignore matching the parameters ---^
}
holi-java
  • 29,655
  • 7
  • 72
  • 83
  • I think you misunderstood the question. I am looking for a way to verify that the lambda passed to `conditionalRun` was called while getting rid of the final, single-element array. – user108471 May 23 '17 at 21:19
  • @user108471 Hi, the edit section is what you want. and your test code has smells you should separate the test by testing behavior. – holi-java May 23 '17 at 21:23
0

You can always replace your lambda expression with an anonymous inner class - or a mock of the right interface (Consumer). And then you can verify your mock as usual.

cyberbrain
  • 3,433
  • 1
  • 12
  • 22
0

that takes a Consumer function - ok... , but how do you pass the Object to that consumer? A Consumer is well a method that returns void and takes item as input. So you either pass item to the method (as an argument) or create it inside the method. Either way you need to mock item and assert that it calls foo only once, not the lambda expression.

Since lambda expressions are de-sugared to methods (with a compiler generated name), I can't really think of a way to mock that - you would need the name to be mocked after all. Things would be more complicated if you use a Method Reference that might not be de-sugared by the compiler at all.

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • All I want to do is to verify that the `conditionalRun` method executes the lambda passed to it. – user108471 May 23 '17 at 21:17
  • Is that your answer? That I should mock item? Can you give a code example of how I would do that? I don't know what you're objecting to about how I described a method that accepts a Consumer. That's exactly how lambdas work in Java 8. – user108471 May 23 '17 at 21:23
  • @user108471 may be Im missing something here. How about an example that I can take into my IDE and understand *exactly* what you mean? – Eugene May 23 '17 at 21:26
  • It sounds like a lot of work on my part to put together an example you can immediately import into your IDE. If you're unfamiliar with Consumers and lambdas, I suggest you take a look at the official documentation (search for Consumer for a good example): https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html – user108471 May 23 '17 at 21:32
  • @user108471 Im not trying to object here actually (or be aggressive in any way), I'm just trying to say that if you provide a `Consumer` you also need to provide the `thing` that this Consumer has to consume. How you pass that Object to `Consumer` we can't really tell, unless you tell us. – Eugene May 23 '17 at 21:45
  • Unfortunately, how the object gets passed to the `Consumer` is the complicated piece that I'm trying to test. It's part of the black box I'm testing in this integration test. – user108471 May 23 '17 at 21:56