我有一个集成@SpringBootTest
,它也暗示着另一个后台线程。
从测试线程(!),一些公共方法调用修改了@SpyBean
版实例(单例@Scope
)的私有字段。
问题在于,从后台线程进行的调用从此更改中看不到任何东西!
经过数小时的调试(例如尝试使用volatile),我浏览了@SpyBean
版的实例,发现所引用的实例和包装实例之间的字段(_state
)确实有所不同(假设spiedInstance
意味着包裹的)以及其他私有字段。
有什么想法吗?
更新: 我简化了环境以帮助复制。
@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
}
}
}
预期:日志中的某些“ TestServiceImpl-0”行之后 出现一些“ TestServiceImpl-1”行。
当前调查结果:如果我从构造函数中移出execute的调用,并放入public方法并通过代理进行调用,它将 按预期工作。
但是上面的TestServiceImpl
是一种简化。在实际代码中,该类在构造函数中订阅了一个事件,该事件将由其他事件触发
线程。此订阅引用的是原始实例,而不是间谍包装程序,因此引发了问题。
UPDATE2:
似乎@SpyBean
创建了一个新实例,而不是使用已经存在的实例
在上下文中。我仍然认为这是一个错误:@SpyBean
应该包装现有实例而不是重复实例,并重复其可能已经使用的字段。
UPDATE3: 突然的结果是情况已被记录在案。 Mockito的文档在监视真实对象部分中包含以下内容:
Mockito 不会将调用委托给传递的真实实例,而是实际上创建它的副本。因此,如果保留真实实例并与之交互,请不要期望间谍知道这些交互及其对真实实例状态的影响。结果是,当一个 unstubbed 方法在间谍中被称为 而在实际实例中被称为不是时,您不会看到对真实实例。
我觉得这确实是一个误导性的实现! 它不是间谍,而是 hiderAndReplacer 功能。 :(
很抱歉浪费您的时间。
答案 0 :(得分:0)
突然发现结果已经记录在案,至少您对问题进行了逆向工程,直到您学会不信任单词的明显含义,而是寻找文档。 Mockito的文档在监视真实对象部分中包含以下内容:
Mockito 不不将调用委派给传递的真实实例,而是实际创建它的副本。因此,如果保留真实实例并与之交互,请不要期望间谍知道这些交互及其对真实实例状态的影响。结果是,当一个 unstubbed 方法在间谍中被称为 而在实际实例中被称为不是时,您不会看到对真实实例。
我觉得这确实是一个误导性的实现!它不是间谍,而是 hiderAndReplacer 功能。 :(
很抱歉浪费您的时间。