Issue
I am testing my Repository class using Mockito, specifically getProducts() functionality:
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()
}
}
This is my test 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))
}
}
And my ApiService:
interface ApiService {
@GET("https://www...")
suspend fun getProducts(): ProductsResponse
}
When I call repository.getProducts()
, it returns null despite the fact, that I explicitly set retrofitService.getProducts()
to return fakeProductsResponse
, which is being called inside repository's getProducts()
method. It should return fakeProductsResponse
, but it returns null.
Am I doing wrong mocking or what the problem can be? Thanks...
EDIT: this is my MainCoroutineRule
, if you need it
@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()
}
}
Solution
It might not be a complete solution to your problem, but what I see is that your MainCoroutineRule
overrides the mainDispatcher Dispatchers.setMain(dispatcher)
.
But in
suspend fun getProducts(): ProductsResponse = withContext(IO)
you are explicitely setting an IO Dispatcher.
I recommend always setting the dispatcher from a property you pass via constructor:
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()
}
}
Having it a default parameter you do not need to pass it in your regular code, but you can exchange it within your unit test:
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)
}
}
For my own unit tests I am using the blocking function of TestCoroutineDispatcher
from the CoroutineRule
like:
@Test
fun aTest() = mainCoroutineRule.dispatcher.runBlockingTest {
val acutal = classUnderTest.callToSuspendFunction()
// do assertions
}
I hope this will help you a bit.
Answered By - ChristianB
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.