Home > Enterprise >  Is this correct way to spy on a message queue consumer?
Is this correct way to spy on a message queue consumer?

Time:08-11

Situation

I was in a Java class and the teachers demonstrated a process like this:

  1. Build a Message Queue, and a Subscriber class. The Subscriber will consume Message Queue inputs and call a certain function in Service class.
  2. Create a test where we Mockito.spy() on the Subscriber class and publish something into the Message Queue. Wait for 2 seconds, and verify if Service method is actually called once.

Subscriber

The Subscriber has a function like this:

    @Bean
    public Consumer<InputVO> subscribeInput() {
        return inputVO -> {
            inputService.someMethod(inputVo);
        };
    }

The Test

The test, then, is basically like this:

    @Autowired
    InputSubscriber inputSubscriber;

    @Autowired
    StreamBridge streamBridge;

    @MockBean
    InputService inputService;

    @Test
    public void subscriberMethod_WillBeCalled_WhenInputReceived() throws InterruptedException {
        InputSubscriber subscriberSpy = Mockito.spy(inputSubscriber);
        doNothing().when(inputService).someMethod(any(InputVO.class));

        InputVO expected = ...;

        streamBridge.send("binding", expected);
        TimeUnit.SECONDS.sleep(2);

        verify(inputService, times(1)).someMethod(expected);

        // will fail with unfinished mock error for below line.
        // verify(subscriberSpy, times(1)).subscribeInput();
    }

The above test then passed, but if we un-comment the last verify() there will always be exception. The actual exception is like this:

org.mockito.exceptions.misusing.UnfinishedVerificationException: 
Missing method call for verify(mock) here:
-> at 
(Here it writes the name and position of test function.)

Example of correct verification:
    verify(mock).doSomething()

I tried my best to keep only the mandatory parts, if it's still too long or falls lacking informations please let me know.

Problem

  1. Does spying with a message queue work?

    • The test is run with annotations of @SpringBootTest and @Import(TestChannelBinderConfiguration.class). So the Subscriber should be actually started right? Then when we publish the expected InputVo to MQ, and Consumer in Subscriber class consumes it, does the spy know? I googled around and seems like spy can only track behaviours directly called through it instead of the object being spied on.
  2. verify() the spy always throw exception during the class.

    • When we call verify() on subscriberSpy it always throw exception, and Mockito reports the reason as Unfinished Mock which says it expects the callee function after verify(). But we already wrote it as verify(subscriberSpy, times(1)).subscribeInput();. The teachers said the reason might be that we mixed Spring MockBean and Spy together. (The spy is spying on an object that injected a @MockBean (Service).), but we never found out the actual reason behind this. I understand that this might be hard to answer because the codes are shortened, but a possible direction pointing on this issue is also welcome.

CodePudding user response:

The answer to your first question (and the title question) is straightforward - the spy you're creating in the test method is not involved in the actual code as it's not injected, passed anywhere etc. If you're using an IDE you can see that if the subscriberSpy verification is commented out, the variable is marked as never used. To work around that you should use the @SpyBean annotation over your inputSubscriber field. Thanks to that the bean will be created and spied on before being injected into the Spring context and you will be able to use it in your test to verify interactions.

The second question is more tricky, because you did not show your full InputSubscriber implementation. You call the verify method on the spy correctly (even though the spy is incorrect - as described above), but the UnfinishedVerificationException also says (the code can be found here):

Also, this error might show up because you verify either of: final/private/equals()/hashCode() methods.

Mocking methods declared on non-public parent classes is not supported.

So I'd assume that the InputSubscriber bean injected into the test falls into one of the above "categories". Node: this could also conflict with the @SpyBean annotation usage described above, but without the code it's hard to judge.

  • Related