У меня проблема с тем, что мои юнит-тесты случайно проваливаются из-за, как мне кажется, условий гонки в сопрограммах.
У меня есть класс, в котором некоторые зависимости, которые высмеиваются в моем UT. (Я использовал как Mockito, так и MockK). При индивидуальном запуске тестов они никогда не заканчиваются ошибкой, даже запуск полного пакета не приводит к их сбою.
Однако, если я выполняю сборку gradlew или полную проверку gradlew (ktlint + detekt + lint + UTs), которые требуют больше ресурсов процессора, то один из тестов случайно завершается неудачей.
Итак, для некоторого фона:
В моем тестируемом классе есть актер, который получает два типа сообщений: постановка в очередь и отправка. Метод register()
, который принимает параметр и ставит его в очередь до тех пор, пока не пройдет заданный таймер, а затем он не будет запущен. Если будет сделан другой вызов register()
, он поставит новый параметр в очередь, отменит сопрограмму с таймером и вызовет новый.
suspend fun register(param: Param) {
// This part suspends until the param is added to the DB
val id = dao.addParam(param)
actor.offer(Message.Enqueue(param))
delayJob?.cancelAndJoin()
delayJob = actor.sendDelayed(this, TIMER, Message.Push)
}
sendDelayed()
это расширение, которое я создал
fun <E> SendChannel<E>.sendDelayed(
scope: CoroutineScope,
time: Long,
element: E
) = scope.launch {
delay(time)
withContext(NonCancellable) {
send(element)
}
}
Одной из зависимостей моего тестируемого класса является CoroutineDispatcher
, который я установил в Dispatchers.Unconfined
в UT.
Например, один из неудачных тестов выглядит примерно так:
@Mock
private lateinit var mockDao: Dao
private lateinit var classUnderTest: ClassUnderTest
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
classUnderTest = ClassUnderTest(mockDao, Dispatchers.Unconfined)
}
@Test
fun `test that fail randomly`() {
val param = Param()
runBlocking {
whenever(mockDao.addParam(param)) doReturn 1
classUnderTest.register(param)
delay(TIMER)
}
verifyBlocking(mockDao) { update(param) }
}
И он терпит неудачу с NullPointerException
. Каким-то образом издевательство не издевается.
Я пробовал с MockK, и результат похож, он не может сказать, что не может найти ответ (по той же причине, фреймворк не может найти насмешку).
В этот момент я уже пытался убрать задержку, сменить диспетчер и многое другое. Кажется, я не могу зависеть от запуска сопрограммы для UT. У меня была похожая проблема: простой launch { val id = dao.get(param) }
приводил к случайному сбою UT, потому что id не был установлен при проверке.
Должно быть, что-то в корне неверно в том, как я работаю с сопрограммами или как я их тестирую. Буду признателен за любые отзывы.
Спасибо