Search code examples
javaspring-bootjunitrabbitmqmockito

Test RabbitTemplate#convertAndSend as lambda with Mockito & JUnit


I am trying to test the RabbitTemplate#convertAndSend method that is written as a lambda, like so:

// other stuff omitted for brevity

        rabbitTemplate.convertAndSend(myQueue, jsonString, message -> {
        message.getMessageProperties().setPriority(priority);
        return message;
        });

// other stuff omitted for brevity

The test case I am trying to do is one where an ArgumentCaptor is being used in order to verify that the method is called with correct parameters.

@Test
public void givenMyNotification_whenElementIsSent_thenSetPriorityAndSendValidParameters() {

final ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
final int expectedPriority = 5;
final Notification expected = TestUtils.getNotification();

testClass.handleNotification(expected);

verify(rabbitTemplate).convertAndSend(captor.capture(), captor.capture(),
    ArgumentMatchers.eq(MessagePostProcessor.class));

// assertThat...
));

}

The test fails at the verify step because the arguments are different.

Wanted:
<Capturing argument>,
<Capturing argument>,
interface org.springframework.amqp.core.MessagePostProcessor

Actual invocation:
"myQueue",
"myJson",
com.example.notification.service.NotificationService$$Lambda$5/73698537@5bda80bf

I have tried several other Matchers from mockito and hamcrest but to no avail.

So, my questions are:

  1. How does one test this kind of a thing?

  2. Is this even a good practice or are there other/better ways to test rabbit template sending?


Solution

  • You almost get it all right, except for the last matcher ArgumentMatchers.eq(MessagePostProcessor.class).

    You actually ask Mockito to match for equatility to the class of the parameter. You should rather match against:

    • the type of the parameter using ArgumentMatchers.any(MessagePostProcessor.class)
    • or an actual value ArgumentMatchers.eq(expectedMessageProcessor) if you happen to have it
    • or an argument captor if you do need to check the value of the parameter

    In this particular case however, if you use the first option, you may run into a compiler issue, as RabbitTemplate class has two similar methods:

    • convertAndSend(String, String, Object)
    • convertAndSend(String, Object, MessagePostProcessor)

    To solve this you can force the type of the second parameter to object like this :

    Mockito.verify(rabbitTemplate).convertAndSend(captor.capture(), (Object) captor.capture(),
                Mockito.any(MessagePostProcessor.class));
    

    Or better, have two different ArgumentCaptors for two different parameters:

    ArgumentCaptor<String> routingKeyCaptor = ArgumentCaptor.forClass(String.class);
    ArgumentCaptor<Object> messageCaptor = ArgumentCaptor.forClass(Object.class);
    
    ...
    
    verify(rabbitTemplate).convertAndSend(routingKeyCaptor.capture(), messageCaptor.capture(), any(MessagePostProcessor.class));
    

    Hope this helps !