Situation
I was in a Java class and the teachers demonstrated a process like this:
- Build a Message Queue, and a
Subscriber
class. TheSubscriber
will consume Message Queue inputs and call a certain function inService
class. - Create a test where we
Mockito.spy()
on theSubscriber
class and publish something into the Message Queue. Wait for 2 seconds, and verify ifService
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
Does spying with a message queue work?
- The test is run with annotations of
@SpringBootTest
and@Import(TestChannelBinderConfiguration.class)
. So theSubscriber
should be actually started right? Then when we publish the expectedInputVo
to MQ, and Consumer inSubscriber
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.
- The test is run with annotations of
verify()
the spy always throw exception during the class.- When we call
verify()
onsubscriberSpy
it always throw exception, and Mockito reports the reason asUnfinished Mock
which 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.