Search code examples
javajunitmockitospring-jms

Mocking MessegePostProcessor Interface in JmsTemplate


I'm new in Mockito and I'm facing an issue regarding a stubbing argument mismatch.

so far I thought this would works fine since in my implementation jmsTemplate is a depedency of jmsTemplateService and everything is injected via @Mock and @InjectMocks

and theoretically I'm mocking the execution of the invoked dependent method

convertAndSend(String destinationName, final Object message, final MessagePostProcessor postProcessor)

main part of test

Message message = Mockito.mock(Message.class);

MessagePostProcessor messagePostProcessor = (x) -> message;

doNothing()
           .when(jmsTemplate)
           .convertAndSend(queue, obj, messagePostProcessor);

jmsTemplateService.sendMessage(queue, obj);

but mockito throws a stubbing argument mismatch error which actually made me understand I'm not stubbing correctly.

- this invocation of 'convertAndSend' method:
    jmsTemplate.convertAndSend(
    "queueA",
     Obj(),
  com.example.JmsTemplateService$$Lambda$383/0x0000000800dd9440@5fb7183b
);
    -> at com.example.JmsTemplateService.sendMessage(JmsTemplateService.java:64)
 - has following stubbing(s) with different arguments:
    1. jmsTemplate.convertAndSend(
    "queueA",
   Obj(),
   com.example.JmsTemplateServiceTest$$Lambda$382/0x0000000800ddb360@74ad8d05
);

it seems the last parameter is the actual problem but I don't know how to capture it without mocking the jmsTemplate dependencies too.

this is the current implementation of the JmsTemplateService.sendMessage(String queue, Obj obj)

 public void sendMessage(String queue, Object obj) {
        
      try {
           jmsTemplate.convertAndSend(queue, obj, message -> {
           //do stuffs before sending the message
           });
      }catch (Exception e) {
       //handle exception
    }

any hint of what could be wrong in mocking the convertAndSend method?

Thank you very much for the help!


Solution

  • it seems the last parameter is the actual problem but I don't know how to capture it without mocking the jmsTemplate dependencies too.

    Actually, you are mocking jmsTemplate, otherwise it'd throw an exception because jmsTemplate variable is not a mock.

    doNothing()
           .when(jmsTemplate) <- a mock is expected to be used here
           .convertAndSend(queue, obj, messagePostProcessor);
    

    You're right that the last parameter is the actual problem since different instances are being used.

    The first instance is defined by your lambda in the test:

    MessagePostProcessor messagePostProcessor = (x) -> message;
    

    And the second one is defined with the other lambda when you perform the call to jmsTemplate.convertAndSend(..., ..., message -> {...})

    Posibly you're getting that error because you're verifying that the jmsTemplate.convertAndSend(...) was called with the params you defined in the tests. Something like:

    verify(jmsTemplate).convertAndSend(queue, obj, messagePostProcessor);
    

    What you can do is expect any() as a 3rd argument in that verify, and expect something like ArgumentsMatchers.eq(queue) and ArgumentsMatchers.eq(obj) as other arguments:

    verify(jmsTemplate).convertAndSend(ArgumentsMatchers.eq(queue), ArgumentsMatchers.eq(obj), any());
    

    Update: possible solution to test the dependencies inside the MessagePostProcessor

    class JmsTemplateServiceTest {
    
        @Mock
        private JmsTemplate jmsTemplate;
    
        @Mock
        private Message message;
    
        @Captor
        private ArgumentCaptor<String> queueCaptor;
    
        @Captor
        private ArgumentCaptor<String> messageCaptor;
    
        @Captor
        private ArgumentCaptor<MessagePostProcessor> messagePostProcessorArgumentCaptor;
    
        @BeforeEach
        void setUp() {
            MockitoAnnotations.initMocks(this);
        }
    
        @Test
        void testJmsTemplateWorksWithCorrectMessagePostProcessor() throws JMSException {
            // Given
            String destinationQueue = "test-queue";
            String testMessage = "test message";
    
            // When
            doNothing().when(jmsTemplate).convertAndSend(destinationQueue, testMessage, message -> message);
    
            // Inline dummy implementation that adds an element to a list defined in this test scope
            List<String> stateCustomDependency = new ArrayList<>();
            CustomDependency customDependency = () -> stateCustomDependency.add("Got executed!");
    
            // Performs service call
            JmsTemplateService jmsTemplateService = new JmsTemplateService(jmsTemplate, customDependency);
            jmsTemplateService.sendMessage(destinationQueue, testMessage);
    
            // Then verify the service actually invokes the jmsTemplate and capture the arguments passed to it
            verify(jmsTemplate).convertAndSend(queueCaptor.capture(), messageCaptor.capture(), messagePostProcessorArgumentCaptor.capture());
    
            // Verify values of queue and message
            assertEquals(destinationQueue, queueCaptor.getValue());
            assertEquals(testMessage, messageCaptor.getValue());
    
            // Verify the MessagePostProcessor instantiated inside the service invokes the lambda dependencies
            MessagePostProcessor messagePostProcessorToVerify = messagePostProcessorArgumentCaptor.getValue();
            messagePostProcessorToVerify.postProcessMessage(message);
            assertEquals(1, stateCustomDependency.size()); // Ensure the CustomDependency was invoked
            assertEquals("Got executed!", stateCustomDependency.get(0)); // Ensure the right String was added by CustomDependency
        }
    }
    

    In case you also need to call some method on Message object, you'll have to add statements like when(message.someMethod(...)).thenReturn(...).

    I hope it helps!