Android 使用 Mockito 进行单元测试:无法获得模拟的正确行为

Android unit testing using Mockito : can't get the right behaviour for mocks

我正在使用 Mockito 测试我的存储库 class,特别是 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()
    }
}

这是我的测试class:

@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))
    }
}

还有我的 ApiService:

interface ApiService {
    @GET("https://www...")
    suspend fun getProducts(): ProductsResponse
}

当我调用 repository.getProducts() 时,它 return 为空,尽管我明确地将 retrofitService.getProducts() 设置为 return fakeProductsResponse,这是在存储库的 getProducts() 方法中调用。它应该 return fakeProductsResponse,但它 return 为空。

我是在做错模拟还是出了什么问题?谢谢...

编辑:这是我的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)

您正在明确设置 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)
    }
}

对于我自己的单元测试,我使用 CoroutineRuleTestCoroutineDispatcher 的阻塞函数,例如:

@Test
fun aTest() = mainCoroutineRule.dispatcher.runBlockingTest {
    val acutal = classUnderTest.callToSuspendFunction()

    // do assertions
}

希望对您有所帮助。