Home > Enterprise >  unit test coroutine nullpointerexception when mocking
unit test coroutine nullpointerexception when mocking

Time:03-09

I am unit testing the following class

class LoadTrendingSearchUseCaseImp @Inject constructor(
    private val searchCriteriaProvider: SearchCriteriaProvider,
    private val coroutineDispatcherProvider: CoroutineDispatcherProvider
) : LoadTrendingSearchUseCase {

    override suspend fun execute(): List<String> {
        return withContext(coroutineDispatcherProvider.io()) {
            searchCriteriaProvider.provideTrendingSearch().trendingSearches
        }
    }
}

interface SearchCriteriaProvider {
    suspend fun provideTrendingSearch(): CatalogSearchPage
}

class SearchCriteriaProviderImp() : SearchCritieraProvider {
    override suspend fun provideTrendingSearch(): CatalogSearchPage {
        return withContext(coroutineDispatcherProvider.io()) {
           /* long running task */
        }
    }
}

interface CoroutineDispatcherProvider {
    fun io(): CoroutineDispatcher = Dispatchers.IO

    fun default(): CoroutineDispatcher = Dispatchers.Default

    fun main(): CoroutineDispatcher = Dispatchers.Main

    fun immediate(): CoroutineDispatcher = Dispatchers.Main.immediate

    fun unconfined(): CoroutineDispatcher = Dispatchers.Unconfined
}

class CoroutineDispatcherProviderImp @Inject constructor() : CoroutineDispatcherProvider

This is my actual test:

class LoadTrendingSearchUseCaseImpTest {
    private val searchCriteriaProvider: SearchCriteriaProvider = mock()
    private val coroutineDispatcherProvider = CoroutineDispatcherProviderImp()
    private lateinit var loadTrendingSearchUseCaseImp: LoadTrendingSearchUseCaseImp

    @Before
    fun setUp() {
        loadTrendingSearchUseCaseImp = LoadTrendingSearchUseCaseImp(
            searchCriteriaProvider,
            coroutineDispatcherProvider
        )
    }

    @Test
    fun `should provide trending searches`() {
        runBlockingTest {
            // Arrange
            // EXCEPTION HERE                whenever(searchCriteriaProvider.provideTrendingSearch().trendingSearches).thenReturn(
                emptyList()
            )

            // Act
            val actualResult = loadTrendingSearchUseCaseImp.execute()

            // Assert
            assertThat(actualResult).isEmpty()
        }
    }
}

The actual error message:

  java.lang.NullPointerException
    .product_search.usecase.imp.LoadTrendingSearchUseCaseImpTest$should provide trending searches$1.invokeSuspend(LoadTrendingSearchUseCaseImpTest.kt:30)

CodePudding user response:

You tried to chain invocations when stubbing a method.

whenever(searchCriteriaProvider.provideTrendingSearch().trendingSearches)
  .thenReturn(emptyList())

During stubbing, the actual methods are being called.

  • searchCriteriaProvider.provideTrendingSearch() returns null, as this call is not stubbed yet
  • subsequent call null.trendingSearches results in NPE

You need to stub each call in the chain

whenever(searchCriteriaProvider.provideTrendingSearch())
  .thenReturn(catalogSearchPage)
whenever(catalogSearchPage.trendingSearches)
  .thenReturn(emptyList())

Obviously, this assumes that

  • catalogSearchPage is also a mock
  • trendingSearches is a property

Alternatively, you can construct a POJO for catalogSearchPage, and return it in the first stubbing.

  • Related