【问题标题】:Mocking suspend function with Mockito returns null使用 Mockito 模拟挂起函数返回 null
【发布时间】:2020-03-13 23:07:28
【问题描述】:

我有以下课程

interface CarsApi {
    suspend fun fetchCar() : Car
}

class FetchCarUseCase(private val carsApi: CarsApi) {
    suspend fun execute: Car = withContext(dispatcherProvider.io()) {
        carsApi.fetchCar()
    }
}

class ViewModel(private val fetchCarUseCase: FetchCarUseCase) {

     private var car: Car

     suspend fun retrieveCar() {
        car = fetchCarUseCase.execute()
     }
}

我想为 viewModel 和 useCase 编写一个 ermetic 测试:

@Test
fun testCarFetching() = runBlockingTest {
  val aCar = Car()
  val mockApi = mock<CarsApi>()
  `when`(mockApi.fetchCar()).thenReturn(aCar)
  val fetchCarUseCase = FetchCarUseCase(mockApi)
  val viewModel = ViewModel(fetchCarUseCase)

  viewModel.retrieveCar()

  /* assert stuff on viewModel.car*/
}

但 viewModel.car 似乎总是为空。在测试主体内 mockApi.fetchCar() 确实检索到提供的值,但在 FetchCarUseCase 内却没有。此外,如果我从界面中删除了暂停关键字,模拟似乎工作正常。

目前,由于其他一些情况,我无法使用 Mockk 库,所以我被 Mockito 困住了。

我错过了什么吗?

使用的依赖: testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.28.2' testImplementation('com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0') { 排除模块:'mockito-core' } testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.2

【问题讨论】:

  • 你能解决这个问题吗?

标签: unit-testing kotlin mockito


【解决方案1】:

如果其他人必须处理这个问题,这里是我构建的基础设施。

首先,在所有启动线程的类中,通过构造函数或属性注入 kotlinx.coroutines.DispatcherProvider。在我的例子中,它只是 useCase,但 viewModel 也可能需要它。

    class FetchCarUseCase(private val dispatcher: CoroutineDispatcher,
                          private val carsApi: CarsApi) {
    suspend fun execute: Car = withContext(dispatcher) {
        carsApi.fetchCar()
    }
}

在单元测试项目中,添加一个辅助规则类,以提取一些功能:

 @ExperimentalCoroutinesApi
class CoroutineTestRule(val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()) : TestWatcher() {

    val testDispatcherProvider = object : DispatcherProvider {
        override fun default(): CoroutineDispatcher = testDispatcher
        override fun io(): CoroutineDispatcher = testDispatcher
        override fun main(): CoroutineDispatcher = testDispatcher
        override fun unconfined(): CoroutineDispatcher = testDispatcher
    }

    override fun starting(description: Description?) {
        super.starting(description)
        Dispatchers.setMain(testDispatcher)
    }

    override fun finished(description: Description?) {
        super.finished(description)
        Dispatchers.resetMain()
        testDispatcher.cleanupTestCoroutines()
    }
}

最后单元测试看起来像这样:

    @ExperimentalCoroutinesApi
    @RunWith(MockitoJUnitRunner::class)
    class ViewModelTest {
        @get:Rule
        var coroutinesTestRule = CoroutineTestRule()

        @Test
    fun testCarFetching() = coroutinesTestRule.testDispatcher.runBlockingTest {
      val aCar = Car()
      val mockApi = mock<CarsApi>()
      `when`(mockApi.fetchCar()).thenReturn(aCar)
      val fetchCarUseCase = FetchCarUseCase(mockApi)
      val viewModel = ViewModel(fetchCarUseCase)

      viewModel.retrieveCar()

      /* assert stuff on viewModel.car*/
    }

   @Test
    fun testCarFetchingError() = coroutinesTestRule.testDispatcher.runBlockingTest {
      val aCar = Car()
      val mockApi = mock<CarsApi>()
      `when`(mockApi.fetchCar()).then {
            throw Exception()
        }

      val fetchCarUseCase = FetchCarUseCase(mockApi)
      val viewModel = ViewModel(fetchCarUseCase)

      viewModel.retrieveCar()

      /* assert stuff on erros*/
    }
}

这样,单元测试中的所有代码都在同一线程和同一上下文中运行。

【讨论】:

猜你喜欢
  • 2019-04-05
  • 1970-01-01
  • 1970-01-01
  • 2020-01-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-08
  • 1970-01-01
相关资源
最近更新 更多