多线程环境中@SpyBean版的类的私有字段

时间:2019-02-27 17:54:37

标签: java spring junit mockito

我有一个集成@SpringBootTest,它也暗示着另一个后台线程。

从测试线程(!),一些公共方法调用修改了@SpyBean版实例(单例@Scope)的私有字段。

问题在于,从后台线程进行的调用从此更改中看不到任何东西!

经过数小时的调试(例如尝试使用volatile),我浏览了@SpyBean版的实例,发现所引用的实例和包装实例之间的字段(_state)确实有所不同(假设spiedInstance意味着包裹的)以及其他私有字段。

exploring spybean instance

有什么想法吗?

更新: 我简化了环境以帮助复制。

@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 功能。 :(

很抱歉浪费您的时间。

1 个答案:

答案 0 :(得分:0)

突然发现结果已经记录在案,至少您对问题进行了逆向工程,直到您学会不信任单词的明显含义,而是寻找文档。 Mockito的文档在监视真实对象部分中包含以下内容:

  

Mockito 不将调用委派给传递的真实实例,而是实际创建它的副本。因此,如果保留真实实例并与之交互,请不要期望间谍知道这些交互及其对真实实例状态的影响。结果是,当一个 unstubbed 方法在间谍中被称为 而在实际实例中被称为不是时,您不会看到对真实实例。

我觉得这确实是一个误导性的实现!它不是间谍,而是 hiderAndReplacer 功能。 :(

很抱歉浪费您的时间。