Situation
I was in a Java class and the teachers demonstrated a process like this:
- Build a Message Queue, and a
Subscriberclass. TheSubscriberwill consume Message Queue inputs and call a certain function inServiceclass. - Create a test where we
Mockito.spy()on theSubscriberclass and publish something into the Message Queue. Wait for 2 seconds, and verify ifServicemethod 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
Does spying with a message queue work?
- The test is run with annotations of
@SpringBootTestand@Import(TestChannelBinderConfiguration.class). So theSubscribershould be actually started right? Then when we publish the expectedInputVoto MQ, and Consumer inSubscriberclass 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.
- The test is run with annotations of
verify()the spy always throw exception during the class.- When we call
verify()onsubscriberSpyit always throw exception, and Mockito reports the reason asUnfinished Mockwhich says it expects the callee function afterverify(). But we already wrote it asverify(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.
- When we call
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.
