I have an integration @SpringBootTest which implies an other background thread too.
From test's thread (!) some public method calls modify a private field of @SpyBean-ed instance (@Scope in singleton).
The problem is that calls from the background thread see nothing from this change!
After hours of debugging (e.g. trying volatile) I explored the @SpyBean-ed instance and found that mentioned field (_state) really differs between referenced and wrapped instances (assuming spiedInstance means the wrapped one) along other private fields too.

Any ideas?
UPDATE: I simplified my environment to help reproducing.
@Service
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public class TestServiceImpl {
private Logger logger = LoggerFactory.getLogger(TestServiceImpl.class);
private final TaskExecutor taskExecutor;
private volatile int value = 0;
private volatile boolean isEnabled = false;
@Autowired
public TestServiceImpl(TaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
taskExecutor.execute(() -> pingToLog());
}
private void pingToLog() {
while(true) {
logger.debug(String.valueOf(value));
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
// no op
}
}
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = BicisoApplication.class)
@DirtiesContext
public class TestServiceTest {
@SpyBean
protected TestServiceImpl testService;
@Test
public void doTest() {
try {
TimeUnit.MILLISECONDS.sleep(3000);
} catch (InterruptedException e) {
// no op
}
testService.setValue(1);
try {
TimeUnit.MILLISECONDS.sleep(3000);
} catch (InterruptedException e) {
// no op
}
}
}
Expected: after some "TestServiceImpl - 0" lines in the log some "TestServiceImpl - 1" lines should appear.
Current investigation results: if I move out execute's call from the constructor and put into public method and call through proxy it will work as expected.
But TestServiceImpl above is a simplification. In real code the class subscribes in constructor to an event which will be triggered by other
threads. This subsciption has a reference to original instance, not the spy wrapper so the problem raises.
UPDATE2:
It seems @SpyBean creates a new instance instead using the one already
in context. I still think it is a bug: @SpyBean should wrap the existing instance not duplicate instance and duplicate its possibly-already-in-use fields.
UPDATE3: Suddently the result is the situation is documented already. Mockito's documentation contains the following under Spying on real objects section:
Mockito does not delegate calls to the passed real instance, instead it actually creates a copy of it. So if you keep the real instance and interact with it, don't expect the spied to be aware of those interaction and their effect on real instance state. The corollary is that when an unstubbed method is called on the spy but not on the real instance, you won't see any effects on the real instance.
I feel this a really missleading implementation! It is not a spy but a hiderAndReplacer feature. :(
Sorry about wasting Your time.
Suddently the result is the situation is documented already, at least You reverse-engineered the problem till You learn not to trust evident meaning of words, but look for docs. Mockito's documentation contains the following under Spying on real objects section:
Mockito does not delegate calls to the passed real instance, instead it actually creates a copy of it. So if you keep the real instance and interact with it, don't expect the spied to be aware of those interaction and their effect on real instance state. The corollary is that when an unstubbed method is called on the spy but not on the real instance, you won't see any effects on the real instance.
I feel this a really missleading implementation! It is not a spy but a hiderAndReplacer feature. :(
Sorry about wasting Your time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With