У меня есть интеграция @SpringBootTest
, которая также подразумевает другой фоновый поток.
Из потока теста (!) Некоторые открытые вызовы метода изменяют закрытое поле экземпляра @SpyBean
-ed (@Scope
вsingleton).
Проблема в том, что вызовы из фонового потока не видят ничего из этого изменения!
После нескольких часов отладки (например, попытки использования volatile) я исследовал экземпляр @SpyBean
-ed и нашелупомянутое поле (_state
) действительно различается между ссылочными и переносимыми экземплярами (при условии, что spiedInstance
означает перенос) и в других закрытых полях.
![exploring spybean instance](https://i.stack.imgur.com/4UwwX.jpg)
Есть идеи?
ОБНОВЛЕНИЕ: Я упростил свою среду, чтобы помочь воспроизведению.
@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 из конструктора и переведу его в публичный метод и вызову через прокси, он будет работать как положено.
Но TestServiceImpl
выше - это упрощение.В реальном коде класс подписывается в конструкторе на событие, которое будет вызвано другими потоками.Эта подписка имеет ссылку на исходный экземпляр, а не на шпионскую оболочку, поэтому возникает проблема.
ОБНОВЛЕНИЕ2: Кажется, @SpyBean
создает новый экземпляр вместо того, чтобы использовать уже существующий в контексте.Я все еще думаю, что это ошибка: @SpyBean
должен обернуть существующий экземпляр, а не дублировать экземпляр, и дублировать его, возможно, уже используемые поля.
ОБНОВЛЕНИЕ3: Неожиданно в результате ситуация уже задокументирована.Документация Mockito содержит следующее в разделе Шпионаж на реальных объектах section:
Mockito не делегирует вызовы переданному реальному экземпляру, вместо этого он фактическисоздает копию этого.Поэтому, если вы сохраняете реальный экземпляр и взаимодействуете с ним, не ожидайте, что шпион узнает об этом взаимодействии и его влиянии на состояние реального экземпляра.Следствие состоит в том, что когда unstubbed метод вызывается на шпионе , но не на реальном экземпляре , вы не увидите никаких эффектов на реальном экземпляре.
Я чувствую, что это действительно ошибочная реализация!Это не шпион , а hiderAndReplacer .: (
Извините, что потратил ваше время.