Я тестирую свой класс репозитория, используя Mockito, в частности функцию getProducts():
class Repository private constructor(private val retrofitService: ApiService) {
companion object {
@Volatile
private var INSTANCE: Repository? = null
fun getInstance(retrofitService: ApiService): Repository {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Repository(retrofitService)
}
INSTANCE = instance
return instance
}
}
}
suspend fun getProducts(): ProductsResponse = withContext(IO) {
retrofitService.getProducts()
}
}
Это мой тестовый класс:
@ExperimentalCoroutinesApi
@RunWith(MockitoJUnitRunner::class)
class RepositoryTest {
// Class under test
private lateinit var repository: Repository
// Executes each task synchronously using Architecture Components.
@get:Rule
val instantExecutorRule = InstantTaskExecutorRule()
// Set the main coroutines dispatcher for unit testing.
@ExperimentalCoroutinesApi
@get:Rule
var mainCoroutineRule = MainCoroutineRule()
@Mock
private lateinit var retrofitService: ApiService
@Before
fun createRepository() {
MockitoAnnotations.initMocks(this)
repository = Repository.getInstance(retrofitService)
}
@Test
fun test() = runBlocking {
// GIVEN
Mockito.`when`(retrofitService.getProducts()).thenReturn(fakeProductsResponse)
// WHEN
val productResponse: ProductsResponse = repository.getProducts()
println("HERE = ${retrofitService.getProducts()}")
// THEN
println("HERE: $productResponse")
MatcherAssert.assertThat(productResponse, `is`(fakeProductsResponse))
}
}
И мой АпиСервис:
interface ApiService {
@GET("https://www...")
suspend fun getProducts(): ProductsResponse
}
Когда я вызываю repository.getProducts()
, он возвращает null, несмотря на то, что я явно установил retrofitService.getProducts()
для возврата fakeProductsResponse
, который вызывается внутри метода getProducts()
репозитория. Он должен вернуть fakeProductsResponse
, но возвращает ноль.
Я неправильно издеваюсь или в чем может быть проблема? Спасибо...
Обновлено: это мой MainCoroutineRule
, если вам это нужно
@ExperimentalCoroutinesApi
class MainCoroutineRule(val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()):
TestWatcher(),
TestCoroutineScope by TestCoroutineScope(dispatcher) {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(dispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
cleanupTestCoroutines()
Dispatchers.resetMain()
}
}
Это может быть не полное решение вашей проблемы, но я вижу, что ваш MainCoroutineRule
переопределяет mainDispatcher Dispatchers.setMain(dispatcher)
.
Но в
suspend fun getProducts(): ProductsResponse = withContext(IO)
вы явно устанавливаете диспетчер ввода-вывода.
Я рекомендую всегда устанавливать диспетчер из свойства, которое вы передаете через конструктор:
class Repository private constructor(
private val retrofitService: ApiService,
private val dispatcher: CoroutineDispatcher) {
companion object {
fun getInstance(retrofitService: ApiService,
dispatcher: CoroutineDispatcher = Dispatchers.IO): Repository {
// ommit code for simplicity
instance = Repository(retrofitService, dispatcher)
// ...
}
}
}
suspend fun getProducts(): ProductsResponse = withContext(dispatcher) {
retrofitService.getProducts()
}
}
Имея параметр по умолчанию, вам не нужно передавать его в свой обычный код, но вы можете заменить его в своем модульном тесте:
class RepositoryTest {
private lateinit var repository: Repository
@get:Rule
var mainCoroutineRule = MainCoroutineRule()
@Mock
private lateinit var retrofitService: ApiService
@Before
fun createRepository() {
MockitoAnnotations.initMocks(this)
repository = Repository.getInstance(retrofitService, mainCoroutineRule.dispatcher)
}
}
Для своих собственных модульных тестов я использую функцию блокировки TestCoroutineDispatcher
из CoroutineRule
, например:
@Test
fun aTest() = mainCoroutineRule.dispatcher.runBlockingTest {
val acutal = classUnderTest.callToSuspendFunction()
// do assertions
}
Я надеюсь, что это поможет вам немного.
Вы правы насчет диспетчеров, это была ошибка, которую я не смог найти. Однако это лишь частично решило проблему, так как похоже, что чего-то еще не хватает, возможно, материала Mockito. В любом случае спасибо, это было бесценно!
Я так думаю, потому что вы запускаете его в withContext(IO). Лучшим решением будет создание новых неограниченных диспетчеров. но другое решение - добавить Thread.sleep(3000) в свой тест