Affects: \ Verified using Spring Boot 2.4.2
I may have found a bug in Spring Test, when using @SpyBean
and a KafkaListener bean. When spying on a bean, there's a case where the spied bean is not the same instance as the bean under test. The spied bean is of course the wrapped object, whereas the bean under test is not wrapped.
In the testcase where it works as expected, both beans are the same instance of the wrapped bean.
I've attached a sample project which reproduces the issue. The test case KafkaJsonValidTest
shows it working as expected, whereas the test case KafkaJsonInvalidTest
reproduces the error.
spybean-bug.tar.gz
Comment From: wilkinsona
Thanks for the sample. The generics in the invalid case don't look quite right to me and I suspect that's what's throwing things out. There doesn't seem to be anything to specify what <T>
should be and they don't seem to provide any type-safety. For example, in JsonConsumer.listen(ConsumerRecord<String, T>)
, T
is unknown so the record's value is really just Object
. The valid case looks better to me as JsonConsumer.listen(ConsumerRecord<String, TestObject>)
provides type information for the record's value.
What's the intent of the invalid case and why not use the approach in the valid case instead?
Comment From: ccoltx
I started with the invalid case first and only added the valid case to prove that it did work. I used generics so I could control which object is actually being used in the testcase itself. I'm testing and playing around with Spring Kafka and TYPE_ID headers (or not).
I added an interface and changed the code to
Comment From: wilkinsona
I've taken a look at this again to refresh my memory and I still think the generics are the problem.
Via component scanning, the context defines a JsonProducer<T>
and a JsonConsumer<T>
. Nothing specifies what T
is, so I believe this effectively becomes Object
. The test uses @SpyBean
to define a JsonConsumer<TestObject>
spy bean. This has stricter type constraints which don't match the broader <Object>
of the existing JsonConsumer<T>
bean so a second JsonConsumer
is registered in the context.
When the test class is being created, it's a JsonConsumer<TestOject>
that needs to be injected into the consumer
field so the spy bean is injected as it's the only one that matches. When the Kafka send and receive is happening, the consumer needs to be able to receive Object
so it's the context's original JsonConsumer<T>
that's called. As a result, the spy doesn't see the consumer being called as it's a different consumer.
The supplied sample passes with the producer and consumer fields generic type changed to <Object>
:
@Autowired
private JsonProducer<Object> producer;
@SpyBean
private JsonConsumer<Object> consumer;
As far as I can tell, things are working as they should here with @SpyBean
only replacing a bean for which it is a type-match, otherwise an additional bean is registered.
Comment From: wilkinsona
We discussed this today and agreed that the generics in the test should be adjusted to match the type of the existing bean in the context so that it can be replaced by the spy.