I am trying to write a unit test for my view model with help of Mockk.
#Test
fun `When loading the ResponseViewState isLoading`() {
val observer = spyk<Observer<ResponseViewState>>(Observer { })
puppiesViewModel.status_.observeForever(observer)
every {
fetchPuppiesUseCase.fetchPuppies()
} returns
Observable.just(Resource.Loading)
puppiesViewModel.fetchPuppies()
val slot = slot<ResponseViewState>()
verify { observer.onChanged(capture(slot)) }
assert(slot.captured.isLoading())
verify { fetchPuppiesUseCase.fetchPuppies() }
}
The error happens when I am creating the observer via spyk.
val observer = spyk<Observer<ResponseViewState>>(Observer { })
The error I am getting is
java.lang.NoClassDefFoundError: com/example/tink/PuppiesViewModelTest$$Lambda$61/0x0000000800176840
at jdk.internal.reflect.GeneratedSerializationConstructorAccessor4.newInstance(Unknown Source)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:500)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:481)
at org.objenesis.instantiator.sun.SunReflectionFactoryInstantiator.newInstance(SunReflectionFactoryInstantiator.java:48)
at io.mockk.proxy.jvm.ObjenesisInstantiator.instanceViaObjenesis(ObjenesisInstantiator.kt:75)
at io.mockk.proxy.jvm.ObjenesisInstantiator.instance(ObjenesisInstantiator.kt:42)
at io.mockk.proxy.jvm.ProxyMaker.instantiate(ProxyMaker.kt:75)
at io.mockk.proxy.jvm.ProxyMaker.proxy(ProxyMaker.kt:42)
at io.mockk.impl.instantiation.JvmMockFactory.newProxy(JvmMockFactory.kt:34)
at io.mockk.impl.instantiation.AbstractMockFactory.newProxy$default(AbstractMockFactory.kt:29)
at io.mockk.impl.instantiation.AbstractMockFactory.spyk(AbstractMockFactory.kt:102)
at com.example.tink.PuppiesViewModelTest.createObserver(PuppiesViewModelTest.kt:99)
at com.example.tink.PuppiesViewModelTest.given loading state, when fetchPuppies called, then isLoading return true(PuppiesViewModelTest.kt:40)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:567)
Caused by: java.lang.ClassNotFoundException: com.example.tink.PuppiesViewModelTest$$Lambda$61.0x0000000800176840
Any idea how to fix or maybe even a better approach for testing?
It seems the change relates to Kotlin and was made at version 1.5.
The change, KT-44912, relates to how the Kotlin compiler generates anonymous class implementing the SAM interface.
You can easily test this by changing your Kotlin version to 1.4.32 (latest 1.4.X).
An easy solution would be changing your code to the following:
val observer = createMockObserver()
#Suppress("ObjectLiteralToLambda")
fun createMockObserver(): Observer<ResponseViewState> {
val observer = object : Observer<ResponseViewState> {
override fun onChanged(t: ResponseViewState?) { }
}
return spyk<Observer<ResponseViewState>>(observer)
}
Alternatively, you can force the Kotlin compiler to use the pre-1.5 anonymous class generation by adding the following to your build.gradle under the android block:
afterEvaluate {
compileDebugUnitTestKotlin {
kotlinOptions {
freeCompilerArgs += [
'-Xsam-conversions=class',
]
}
}
}
Related
I am trying to create a unit test using Spek framework and nhaarman mockito kotlin in my Android Kotlin project. The problem is that when there is nested suspend method I don't know how to mock response.This is how I'm trying
I defined:
val testCoroutineDispatcher = TestCoroutineDispatcher()
val testCoroutineScope = TestCoroutineScope(testCoroutineDispatcher)
and before any describe
beforeGroup {
Dispatchers.setMain(testCoroutineDispatcher) //not sure if this is working properly
}
afterGroup {
Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
testCoroutineScope.cleanupTestCoroutines()
}
and this is my group
describe("Test view model") {
val contentRepository by memoized(CachingMode.SCOPE) { mock<ContentRepository>() }
val contentViewModel by memoized(CachingMode.SCOPE) {
ContentViewModel(contentRepository)
}
describe("When something happens") {
beforeGroup {
testCoroutineScope.runBlockingTest {
whenever(contentRepository.fetchAllContents(0, 10))
.thenReturn(Result.success(content))//This is suspend
contentViewModel.setContentPage(0)
}
}
it("should fetch all content from repository with page 0") {
verifyBlocking(contentRepository) {
fetchAllClassContents(0, 10)
}
}
}
}
})
But Im getting the following error
Argument(s) are different! Wanted:
classContentRepository.fetchAllClassContents(
0,
10,
Continuation at viewmodel.ContentViewModelSpek$1$3$1$4$1.invokeSuspend(ContentViewModelSpek.kt:92)
);
-> at repository.ContentRepository.fetchAllClassContents(ContentRepository.kt:23)
Actual invocation has different arguments:
contentRepository.fetchAllContents(
0,
10,
Continuation at viewmodel.ContentViewModel$setContentPage$1.invokeSuspend(ContentViewModel.kt:26)
);
It seem like mock, method execution and assertion are running in different scopes
I can't find any guide that helps me create test with coroutine
Thanks in advance
I am developing news android app and I have implemented Koin modules but I am getting the following exception
executor.executeLifecycleState(TransactionExecutor.java:147)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:73)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1858)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:201)
at android.app.ActivityThread.main(ActivityThread.java:6820)
... 3 more
Caused by: org.koin.core.error.NoBeanDefFoundException: No definition found for class:'yodgorbek.komilov.musobaqayangiliklari.repository.BBCRepository' & qualifier:'bbcModules'. Check your definitions!
at org.koin.core.scope.Scope.throwDefinitionNotFound(Scope.kt:247)
at org.koin.core.scope.Scope.resolveInstance(Scope.kt:216)
at org.koin.core.scope.Scope.get(Scope.kt:181)
at yodgorbek.komilov.musobaqayangiliklari.di.application.module.BbcModulesKt$bbcModules$1$2.invoke(bbcModules.kt:16)
at yodgorbek.komilov.musobaqayangiliklari.di.application.module.BbcModulesKt$bbcModules$1$2.invoke(Unknown Source:4)
at org.koin.core.instance.InstanceFactory.create(InstanceFactory.kt:50)
... 48 more
below my bbcModules.kt where I have implemented BBCSportViewModel logic
val bbcModules = module {
factory(named("bbcModules")) { (BBCRepositoryImpl(bbcsportNewsApi = get())) }
// Tells Koin how to create an instance of BBCRepository
viewModel { BBCSportViewModel(bbcRepository = get(named("bbcModules")))
}
}
below BBCRepository.kt
interface BBCRepository {
// Suspend is used to await the result from Deferred
suspend fun getBBCList(): UseCaseResult<List<Article>>
}
#Suppress("UNCHECKED_CAST")
class BBCRepositoryImpl(private val bbcsportNewsApi: SportNewsInterface) : BBCRepository {
override suspend fun getBBCList(): UseCaseResult<List<Article>> {
return try {
val result = bbcsportNewsApi.getBBCSport().body()!!.articles
UseCaseResult.Success(result)
} catch (ex: Exception) {
UseCaseResult.Error(ex)
}
}
}
I want to know where exactly I am making mistake what I have to fix exception I have followed many StackOverflow answer it did not solve my problem
The stacktrace says that no definition for BBCRepository was found.
In your koin module you have a factory of BBCRepositoryImpl and not BBCRepository:
factory(named("bbcModules")) {
(BBCRepositoryImpl(bbcsportNewsApi = get()))
}
In order for it to work, you should make sure you are providing the interface in koin with:
factory<BBCRepository>(named("bbcModules")) {
BBCRepositoryImpl(bbcsportNewsApi = get())
}
or
factory(named("bbcModules")) {
(BBCRepositoryImpl(bbcsportNewsApi = get())) as BBCRepository
}
I'm trying to stub a kotlin extension .map:
private fun stubGetUsers(users: List<User>) {
val genericRawResult = mock<GenericRawResults<User>>()
whenever(genericRawResult.map { User() }).thenReturn(users)
usersDao.stub { on { it.queryRaw("", RawRowMapper { _, _ -> User() }) }.doReturn(genericRawResult) }
}
When I run the test, I'm getting a NullPointerException:
public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C {
for (item in this)
destination.add(transform(item))
return destination
}
at line: for (item in this)
So I tried to mock the _Collections.kt class mapTo function:
val destination = arrayListOf<User>()
whenever<List<User>>(genericRawResult.mapTo(destination) { User() }).thenReturn(destination)
But still getting the error:
java.lang.NullPointerException
at com.myapp.UserRepositoryTest.stubGetUsers(UserRepositoryTest.kt:144)
at com.myapp.UserRepositoryTest.testGetAllUsers(UserRepositoryTest.kt:66)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
In order to get a detailed error, I've made the following changes:
genericRawResult.stub { onGeneric { it.mapTo(arrayListOf()) { User() } }.doReturn(arrayListOf()) }
genericRawResult.stub { on { it.map { User() } }.doReturn(users) }
And this is the error that I get:
org.mockito.exceptions.misusing.WrongTypeOfReturnValue:
ArrayList cannot be returned by iterator()
iterator() should return Iterator
***
If you're unsure why you're getting above error read on.
Due to the nature of the syntax above problem might occur because:
1. This exception *might* occur in wrongly written multi-threaded tests.
Please refer to Mockito FAQ on limitations of concurrency testing.
2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies -
- with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.
The error I have:
The code with the error:
#RunWith(PowerMockRunner::class)
#PrepareForTest(PotatoProvider::class, PotatoConsumer::class)
class WantedButNotInvoked {
#Mock
lateinit var potatoConsumer: PotatoConsumer
#Test
fun potato() {
Observable.just(Potato()).subscribe(potatoConsumer)
verify(potatoConsumer).accept(Potato())
//verify(potatoConsumer).accept(any()) //-> This fails too with the same reason
}
}
data class Potato(val value: Int = 1)
class PotatoConsumer : Consumer<Potato> {
override fun accept(t: Potato?) {
println(t)
}
}
So I making subscribe with this mock(potatoConsumer), and the rxJava have called 'accept', and mockito mark it as interaction, but mockito thinks this interaction is not what I'm expecting, why?
Versions of libraries used her:
mockitoVersion = '2.8.9'
mockitoAndroidVersion = '2.7.22'
powerMockVersion="2.0.2"
kotlinMockito="2.1.0"
rxKotlin = "2.3.0"
rxJavaVersion = "2.2.10"
Kinda workaround
Some fields mocked by powermock, fails on 'verify', but fields mocked with mockito is not;
Mockito can't mock not opened fields, without mock-maker-inline, but mockito conflicts with Powermock mock-maker-inline;
Powermock can delegate calls of mock-maker-inline to other mock-maker-inline(https://github.com/powermock/powermock/wiki/PowerMock-Configuration)
Use Mockito.mock on the failed fields instead of #Mock/Powermock mock injection
Example of the "green" potato test method using PowerMockRunner
#Test
fun potato() {
potatoConsumer = mock() // <-
Observable.just(Potato()).subscribe(potatoConsumer)
verify(potatoConsumer).accept(potato)
}
I am not familiar with PowerMock but I tried this test and it passes:
#Test
fun potato() {
fakePotatoProvider = Mockito.mock(PotatoProvider::class.java)
potatoConsumer = Mockito.mock(PotatoConsumer::class.java)
`when`(fakePotatoProvider.getObservable()).thenReturn(Observable.just(Potato()))
fakePotatoProvider.getObservable().subscribe(potatoConsumer)
verify(potatoConsumer).accept(Potato())
}
Maybe because you aren't passing the same instance of Potato(). Try to refactor your code to this
#Test
fun potato() {
val testPotato = Potato()
`when`(fakePotatoProvider.getObservable()).thenReturn(Observable.just(testPotato))
fakePotatoProvider.getObservable().subscribe(potatoConsumer)
verify(potatoConsumer).accept(testPotato)
}
As I mentioned above, the reason why it might be failing is the constant creation of new instances when passing your Potato object, hance that comparison fails.
My ViewModel needs repository & genre through constructor. repository is provided by Koin & genre string is provided from activity
// Main app module
val MovieListModule: Module = applicationContext {
// provide repository
bean {
DummyMovieListRepository() as MovieListRepository
}
// provides ViewModel
viewModel { params: ParameterProvider ->
MovieListViewModel(respository = get(), genre = params["key.genre"])
}
}
//Module list for startKoin()
val appModules = listOf(MovieListModule)
//in activity
val viewModel = getViewModel<MovieListViewModel> {
mapOf("key.genre" to "Action / Drama")
}
// dry run test which fails
class KoinDryRunTest : KoinTest {
#Test
fun dependencyGraphDryRun() {
startKoin(list = appModules)
dryRun()
}
}
// some error log
org.koin.error.MissingParameterException: Parameter 'key.genre' is missing
at org.koin.dsl.context.ParameterHolder.get(ParameterHolder.kt:46)
org.koin.error.BeanInstanceCreationException: Can't create bean Factory[class=io.github.karadkar.popularmovies.MovieListViewModel, binds~(android.arch.lifecycle.ViewModel)] due to error :
org.koin.error.MissingParameterException: Parameter 'key.genre' is missing
here Koin (v 0.9.3) injection inactivity works as expected but the dry run test fails as it can't find parameter key.genre. Check full error-log
Is there any way to mock/provide key.genre value to dry run test?
full app source
as Arnaud Giuliani pointed on twitter. dryRun accepts lambda function for parameters
class KoinDryRunTest : KoinTest {
#Test
fun dependencyGraphDryRun() {
startKoin(list = appModules)
dryRun() {
mapOf("key.genre" to "dummy string")
}
}
}