Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockito verify that a specific lambda has been passed as an argument in mock's method

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,); ?

like image 459
JavaSloth Avatar asked Jan 04 '17 11:01

JavaSloth


1 Answers

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.

like image 97
hotzst Avatar answered Sep 20 '22 12:09

hotzst