При использовании Mockito verify
с таймаутом для ожидания вызова асинхронного метода иногда происходит сбой, если метод синхронизирован.
Я наблюдал эффект с несколькими версиями Eclipse, включая 2020-06 (4.16.0) и Mockito 3.1.0 и 3.4.6. Пример ниже иллюстрирует проблему (и тест, и испытуемый находятся в одном классе для простоты). Почему это не удается?
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class A {
@Mock
private A a;
public synchronized void f() {
// Do stuff
}
@Test
void test() {
// Call a.f() asynchronously and with a small delay
// (to make the test thread yield).
new Thread(() -> {
try {
Thread.sleep(100);
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
}
a.f();
}).start();
// This verification fails, but it should pass.
Mockito.verify(a, Mockito.timeout(2000)).f();
}
}
Примечательные моменты:
- Эффект воспроизводится не полностью. Фактически, мои примеры из реальной жизни никогда не выходят из строя на одних компьютерах, в то время как на других они всегда терпят неудачу, что указывает на состояние гонки.
- Удаление ключевого слова
synchronized
решает проблему, даже при замене его на synchronized(this)
блок в теле метода. - Если класс, содержащий синхронизированный метод, реализует интерфейс, фиксация интерфейса вместо конкретного класса решает проблему.
- Удаление спящего режима в приведенном выше примере, следовательно Повышение вероятности того, что вызов
f
завершится до вызова verify
, решает проблему. - Поведение подозрительно похоже на проблему, описанную (и предположительно решенную) здесь: https://github.com/mockito/mockito/issues/253.