unit test coroutine nullpointerexception when mocking - unit-testing

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)

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

Unit testing and high-order functions in Kotlin

So i have a base class:
abstract class BaseService<M : MyMetric>(private val otherService: OtherService) {
abstract fun <T> monitor(metric: M, action: () -> T): T
protected fun <T> monitorDefault(metric: M, action: () -> T): T {
val begin = System.currentTimeMillis()
return try {
notifyEvent(metric.total)
val result = action()
notifyEvent(metric.success)
result
} catch (e: CommonException) {
//handling
}
}
and simple implementation:
class PrintMetricService(otherService: OtherService): BaseService<PrintMetric>(otherService) {
override fun <T> monitor(metric: PrintMetric, action: () -> T) : T {
return monitorDefault(metric, action)
}
}
then i use this method:
#Service
class PrintReportService(private val reportService: ReportService,
private val metricService: MetricService<PrintMetric>) {
fun getReport(): String {
return metricService.monitor(PrintMetric) {
val response = try {
reportService.renderPdf(jsonRequest, TEMPLATE_CODE, channel)
} catch (e: Exception) {
throw CommonException(PRINT_SERVICE_ERROR_EXCEPTION_CODE)
}
response.body.data!!
}
}
now I want to write a unit test for getReport() (we use TestNg and mockito in project):
class PrintReportServiceImplTest {
#InjectMocks
private lateinit var printReportService: PrintReportServiceImpl
#Mock
private lateinit var reportService: ReportService
#Mock
private lateinit var metricService: PrintMetricService
#BeforeMethod
fun setUp() {
openMocks(this)
whenever(reportService.renderPdf().thenReturn("response (base64)")
}
#Test
fun getReportTest() {
val report = printReportService.getReport()
assertNotNull(report)
}
}
the test crashes with an error: java.lang.AssertionError: expected object to not be null
But if i add inline when override monitor(metric: M, action: () -> T) in PrintMetricService the test will pass. What could be the problem? Is this a feature of the high-order functions in Kotlin?

mockk, how to use slot for MutableMap<String, String>

using mockk 1.9.3
having a function to be verified
class EventLogger private constructor()
fun logUserEvent(eventName: String?, eventParamMap: MutableMap<String, String>?) {
......
internaLogEventImpl(eventName, eventParamMap)
}
internal fun internaLogEventImpl(eventName: String?, customParams: MutableMap<String, String>?) {
......
}
companion object {
#Volatile
private var sEventLoggerSingleton: EventLogger? = null
#JvmStatic
val instance: EventLogger
get() {
if (sEventLoggerSingleton == null) {
sEventLoggerSingleton = EventLogger()
}
return sEventLoggerSingleton!!
}
}
got compiler error at every {eventLogger.internaLogEventImpl(any(), mapSlot)}
Type mismatch.
Required: MutableMap<String, String>?
Found: CapturingSlot<MutableMap<String, String>>
when trying this below :
class TestK {
lateinit var eventLogger: EventLogger
lateinit var application: Application
val mapSlot = slot<MutableMap<String, String>>()
#Before
fun setUp() {
application = ApplicationProvider.getApplicationContext<Application>()
eventLogger = mockk.spyk(EventLogger.instance)
ReflectionHelpers.setStaticField(EventLogger::class.java, "sEventLoggerSingleton", eventLogger)
}
#After
fun cleanUp() {
ReflectionHelpers.setStaticField(EventLogger::class.java, "sEventLoggerSingleton", null)
}
#Test
fun logNotificationStatusChange_with_enabled_WhenCalled_ShouldLog() {
val testMap = hashMapOf("action" to "open")
every {eventLogger.internaLogEventImpl(any(), mapSlot)} answers {
println(mapSlot.captured)
assert(mapSlot.captured["action"] == "open")
}
eventLogger.logUserEvent("test_event", testMap)
}
}
You need to use capture (see Mockk's Capturing section).
So for your case capture(mapSlot) should work.
eventLogger.internaLogEventImpl(any(), capture(mapSlot))
Before trying to mock on complex code, It would be better to learn on easier example.
Here is a working example to mock a private call on an object with mockk.
object MyLogger {
fun logUserEvent(event: String?, map: MutableMap<String, String>?) {
// turns the event string into an uppercase string.
internaLogEventImpl(event?.toUpperCase(), map)
}
private fun internaLogEventImpl(event: String?, map: MutableMap<String, String>?): Unit =
throw Exception("real implementation")
}
How to test and mock the internal function so we don't throw the exception.
#Test
fun `test logger internal`() {
val expectedMap = mutableMapOf("a" to "b")
val expectedEvent = "EVENT"
val mock = spyk(MyLogger, recordPrivateCalls = true)
justRun { mock["internaLogEventImpl"](expectedEvent, expectedMap) }
// or justRun { mock["internaLogEventImpl"](any<String>(), any<MutableMap<String, String>>()) }
mock.logUserEvent("event", expectedMap)
verify { mock["internaLogEventImpl"](expectedEvent, expectedMap) }
}
Here logUserEvent calls the real implementation and internaLogEventImpl calls the mock implementation.
if justRun { mock["internaLogEventImpl"](expectedEvent, expectedMap) } is not called (or wrong because the argument doesn't match), the real implementation will be call. Here it will throw Exception("real implementation").
Please try and modify values to check different behaviors.

Unable to mock a method - Mockito - Kotlin

I am trying to mock a method (getQualityControlCheckDataForUplift(ArgumentMatchers.any(), ArgumentMatchers.anyFloat())) to return qcchecks which I have defined in my test case but I get an error.
Any suggestions on where I am doing it wrong please
org.mockito.exceptions.base.MockitoException: Unable to create mock instance of type 'QualityControlChecksDataProvider'
QualityControlChecksDataProvider
class QualityControlChecksDataProvider #Inject constructor(
private val offlineDataStorage: OfflineDataStorage,
private val app: App
) {
private val mapOfQCChecksForMilestone = LinkedHashMap<String, ArrayList<QualityControlCheck>?>()
fun getQualityControlCheckDataForUplift(qualityControlMilestone: QualityControlMilestone, uplift: Float):
ArrayList<QualityControlCheck>? {
val qcChecksForUplift: ArrayList<QualityControlCheck>? = ArrayList()
val qcChecksForMilestone = mapOfQCChecksForMilestone[qualityControlMilestone.milestoneText]
qcChecksForMilestone?.forEach {
if (it.uplift == uplift) qcChecksForUplift?.add(it)
}
return qcChecksForUplift
}
}
In my unit test this is how I am trying to mock
#Test
fun `upliftedVolumeUpdated abcd` () {
val qualityControlCheckDataProvider = spy(QualityControlChecksDataProvider::class.java)
every(qualityControlCheckDataProvider.getQualityControlCheckDataForUplift(ArgumentMatchers.any(), ArgumentMatchers.anyFloat())).thenReturn(qualityControlChecks)
}
I don't think a spy is what you want here I think you want a mock. Give this a try
#Test
fun `upliftedVolumeUpdated abcd` () {
val qualityControlDataProviderMock = mock<QualityControlChecksDataProvider> {
on { getQualityControlCheckDataForUplift(any(), any()) } doReturn qualityControlChecks
}
}

Mocking suspend function with Mockito returns null

I have the following classes
interface CarsApi {
suspend fun fetchCar() : Car
}
class FetchCarUseCase(private val carsApi: CarsApi) {
suspend fun execute: Car = withContext(dispatcherProvider.io()) {
carsApi.fetchCar()
}
}
class ViewModel(private val fetchCarUseCase: FetchCarUseCase) {
private var car: Car
suspend fun retrieveCar() {
car = fetchCarUseCase.execute()
}
}
I want to write an ermetic test for the viewModel and the useCase:
#Test
fun testCarFetching() = runBlockingTest {
val aCar = Car()
val mockApi = mock<CarsApi>()
`when`(mockApi.fetchCar()).thenReturn(aCar)
val fetchCarUseCase = FetchCarUseCase(mockApi)
val viewModel = ViewModel(fetchCarUseCase)
viewModel.retrieveCar()
/* assert stuff on viewModel.car*/
}
But the viewModel.car always seems to be null. Inside the test body mockApi.fetchCar() does retrieve the provided value, but inside the FetchCarUseCase it does not. Also if I remove the suspend keyword from the interface, the mocking seems to be working fine.
At the moment, due to some other conditions I cannot use Mockk library, so I'm stuck with Mockito.
Am I missing something?
The used dependencies:
testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.28.2'
testImplementation('com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0') {
exclude module: 'mockito-core'
}
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.2
In case anyone else has to deal with this problem, here is the infrastructure I have build.
First, in all the classes that launch threads inject through the constructor or property a kotlinx.coroutines.DispatcherProvider. In my case it was just the useCase, but the viewModel might require it, as well.
class FetchCarUseCase(private val dispatcher: CoroutineDispatcher,
private val carsApi: CarsApi) {
suspend fun execute: Car = withContext(dispatcher) {
carsApi.fetchCar()
}
}
In the unit tests project, add a helper rule-class, in order to extract some functionality:
#ExperimentalCoroutinesApi
class CoroutineTestRule(val testDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()) : TestWatcher() {
val testDispatcherProvider = object : DispatcherProvider {
override fun default(): CoroutineDispatcher = testDispatcher
override fun io(): CoroutineDispatcher = testDispatcher
override fun main(): CoroutineDispatcher = testDispatcher
override fun unconfined(): CoroutineDispatcher = testDispatcher
}
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(testDispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
}
And finally the unit test looks like this:
#ExperimentalCoroutinesApi
#RunWith(MockitoJUnitRunner::class)
class ViewModelTest {
#get:Rule
var coroutinesTestRule = CoroutineTestRule()
#Test
fun testCarFetching() = coroutinesTestRule.testDispatcher.runBlockingTest {
val aCar = Car()
val mockApi = mock<CarsApi>()
`when`(mockApi.fetchCar()).thenReturn(aCar)
val fetchCarUseCase = FetchCarUseCase(mockApi)
val viewModel = ViewModel(fetchCarUseCase)
viewModel.retrieveCar()
/* assert stuff on viewModel.car*/
}
#Test
fun testCarFetchingError() = coroutinesTestRule.testDispatcher.runBlockingTest {
val aCar = Car()
val mockApi = mock<CarsApi>()
`when`(mockApi.fetchCar()).then {
throw Exception()
}
val fetchCarUseCase = FetchCarUseCase(mockApi)
val viewModel = ViewModel(fetchCarUseCase)
viewModel.retrieveCar()
/* assert stuff on erros*/
}
}
This way all the code in the unit tests runs on the same thread and in the same context.

Unit test with Coroutines and Retrofit

I created an App using coroutines and retrofit and it works fine. The problem comes when I try to create UT for the Presenter. Here how I made the presenter:
class MainPresenter : ViewModel() {
private val compositeDisposable = CompositeDisposable()
private val heroesRepository: HeroesRepository = heroesRepositoryModel.instance()
private lateinit var listener: ActivityStatesListener
fun setActivityListener(listener: ActivityStatesListener) {
this.listener = listener
}
fun getHeroesFromRepository(page: Int) {
GlobalScope.launch(Dispatchers.Main) {
try {
val response = heroesRepository.getHeroes(page)
listener.onHeroesReady(response.data.results)
} catch (e: HttpException) {
listener.onError(e.message())
} catch (e: Throwable) {
listener.onError(e.message)
}
}
}
override fun onCleared() {
super.onCleared()
compositeDisposable.dispose()
}
}
I started to make the UT for it and I made a small test but is giving me the following error: java.lang.IllegalStateException: Module with the Main dispatcher had failed to initialize
class HeroesDataSourceTest {
val heroesRepository: HeroesRepository = mock(HeroesRepository::class.java)
#Mock
lateinit var activityListener: ActivityStatesListener
val hero = Heroes.Hero(1, "superman", "holasuperman", 1, null, null)
val results = Arrays.asList(hero)
val data = Heroes.Data(results)
val dataResult = Heroes.DataResult(data)
private val mainPresenter = MainPresenter()
#Before
fun initTest() {
MockitoAnnotations.initMocks(this)
}
#Test
fun testLoadInitialSuccess() = runBlocking(Dispatchers.Main) {
`when`(heroesRepository.getHeroes(0)).thenReturn(dataResult)
mainPresenter.getHeroesFromRepository(0)
verify(activityListener).onHeroesReady(dataResult.data.results)
}
}
Is clear that Dispatcher.Main is giving problems but I have no clue how to solve it.
EDIT
The repository used is the following:
class HeroesRepository {
val privateKey = "5009bb73066f50f127907511e70f691cd3f2bb2c"
val publicKey = "51ef4d355f513641b490a80d32503852"
val apiDataSource = DataModule.create()
val pageSize = 20
suspend fun getHeroes(page: Int): Heroes.DataResult {
val now = Date().time.toString()
val hash = generateHash(now + privateKey + publicKey)
val offset: Int = page * pageSize
return apiDataSource.getHeroes(now, publicKey, hash, offset, pageSize).await()
}
fun generateHash(variable: String): String {
val md = MessageDigest.getInstance("MD5")
val digested = md.digest(variable.toByteArray())
return digested.joinToString("") {
String.format("%02x", it)
}
}
}
I assume that heroesRepository.getHeroes(page) marked as suspend, so it will suspend the coroutine and not block the Main thread.
Try to follow the next approach:
// add `CoroutineContext` to the constructor to be replaceable from the tests
class MainPresenter(private val uiContext: CoroutineContext = Dispatchers.Main)
: ViewModel(), CoroutineScope {
private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = uiContext + job
fun getHeroesFromRepository(page: Int) {
// use local scope to launch a coroutine
launch {
try {
val response = heroesRepository.getHeroes(page)
listener.onHeroesReady(response.data.results)
} catch (e: HttpException) {
listener.onError(e.message())
} catch (e: Throwable) {
listener.onError(e.message)
}
}
}
override fun onCleared() {
super.onCleared()
job.cancel()
}
// ...
}
In the test class replace uiContext with another CoroutineContext:
class HeroesDataSourceTest {
// ... initializations
#Test
fun testLoadInitialSuccess() = runBlocking {
`when`(heroesRepository.getHeroes(0)).thenReturn(dataResult)
mainPresenter = MainPresenter(Dispatchers.Unconfined).apply {
getHeroesFromRepository(0)
}
// ... your tests here
}
}