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)
}
}
对于我自己的单元测试,我使用 CoroutineRule
中 TestCoroutineDispatcher
的阻塞函数,例如:
@Test
fun aTest() = mainCoroutineRule.dispatcher.runBlockingTest {
val acutal = classUnderTest.callToSuspendFunction()
// do assertions
}
希望对您有所帮助。
我正在使用 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)
}
}
对于我自己的单元测试,我使用 CoroutineRule
中 TestCoroutineDispatcher
的阻塞函数,例如:
@Test
fun aTest() = mainCoroutineRule.dispatcher.runBlockingTest {
val acutal = classUnderTest.callToSuspendFunction()
// do assertions
}
希望对您有所帮助。