Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UnitTest coroutines Kotlin usecase MVP

I am trying to mock a response from my usecases, this usecase works with coroutines.

fun getData() {
    view?.showLoading()
    getProductsUseCase.execute(this::onSuccessApi, this::onErrorApi)
}

My useCase is injected on presenter.

GetProductsUseCase has this code:

class GetProductsUseCase (private var productsRepository: ProductsRepository) : UseCase<MutableMap<String, Product>>() {

    override suspend fun executeUseCase(): MutableMap<String, Product> {
        val products =productsRepository.getProductsFromApi()
        return products
    }
}

My BaseUseCase

abstract class UseCase<T> {

    abstract suspend fun executeUseCase(): Any

    fun execute(
        onSuccess: (T) -> Unit,
        genericError: () -> Unit) {
        GlobalScope.launch {
            val result = async {
                try {
                    executeUseCase()
                } catch (e: Exception) {
                    GenericError()
                }
            }
            GlobalScope.launch(Dispatchers.Main) {
                when {
                    result.await() is GenericError -> genericError()
                    else -> onSuccess(result.await() as T)
                }
            }
        }
    }

}

This useCase call my repository:

override suspend fun getProductsFromApi(): MutableMap<String, Product> {
    val productsResponse = safeApiCall(
        call = {apiService.getProductsList()},
        error = "Error fetching products"
    )
    productsResponse?.let {
                    return productsMapper.fromResponseToDomain(it)!!
    }
    return mutableMapOf()
}

Y try to mock my response but test always fails.

@RunWith(MockitoJUnitRunner::class)
class HomePresenterTest {

    lateinit var presenter: HomePresenter

    @Mock
    lateinit var view: HomeView

    @Mock
    lateinit var getProductsUseCase: GetProductsUseCase

    @Mock
    lateinit var updateProductsUseCase: UpdateProductsUseCase

    private lateinit var products: MutableMap<String, Product>

    private val testDispatcher = TestCoroutineDispatcher()
    private val testScope = TestCoroutineScope(testDispatcher)

    @Mock
    lateinit var productsRepository:ProductsRepositoryImpl

    @Before
    fun setUp() {
        Dispatchers.setMain(testDispatcher)
        products = ProductsMotherObject.createEmptyModel()
        presenter = HomePresenter(view, getProductsUseCase, updateProductsUseCase, products)
    }

    @After
    fun after() {
        Dispatchers.resetMain()
        testScope.cleanupTestCoroutines()
    }

    //...

    @Test
    fun a() = testScope.runBlockingTest {
        setTasksNotAvailable(productsRepository)
        presenter.getDataFromApi()

        verify(view).setUpRecyclerView(products.values.toMutableList())
    }

    private suspend fun setTasksNotAvailable(dataSource: ProductsRepository) {
        `when`(dataSource.getProductsFromApi()).thenReturn((mutableMapOf()))
    }
}

I don't know what is happening. The log says:

"Wanted but not invoked:
view.setUpRecyclerView([]);
-> at com.myProject.HomePresenterTest$a$1.invokeSuspend(HomePresenterTest.kt:165)

However, there was exactly 1 interaction with this mock:
view.showLoading();"
like image 412
Aris Guimerá Avatar asked Jul 03 '19 21:07

Aris Guimerá


Video Answer


1 Answers

The problem is with how you create your GetProductsUseCase.

You're not creating it with the mocked version of your ProductsRepository, yet you're mocking the ProductsRepository calls.

Try to create the GetProductsUseCase manually and not using a @Mock

// no @Mock
lateinit var getProductsUseCase: GetProductsUseCase


@Before
fun setUp() {
    // ...
    // after your mocks are initialized...
    getProductsUseCase = GetProductsUseCase(productsRepository) //<- this uses mocked ProductsRepository
}
like image 103
Bartek Lipinski Avatar answered Oct 24 '22 21:10

Bartek Lipinski