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

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

Answer Checked By – Senaida (Easybugfix Volunteer)

Leave a Reply

(*) Required, Your email will not be published