I have a problem with capturing the Class argument via ArgumentCaptor. My test class looks like this:
#RunWith(RobolectricGradleTestRunner::class)
#Config(sdk = intArrayOf(21), constants = BuildConfig::class)
class MyViewModelTest {
#Mock
lateinit var activityHandlerMock: IActivityHandler;
#Captor
lateinit var classCaptor: ArgumentCaptor<Class<BaseActivity>>
#Captor
lateinit var booleanCaptor: ArgumentCaptor<Boolean>
private var objectUnderTest: MyViewModel? = null
#Before
fun setUp() {
initMocks(this)
...
objectUnderTest = MyViewModel(...)
}
#Test
fun thatNavigatesToAddListScreenOnAddClicked(){
//given
//when
objectUnderTest?.addNewList()
//then
verify(activityHandlerMock).navigateTo(classCaptor.capture(), booleanCaptor.capture())
var clazz = classCaptor.value
assertNotNull(clazz);
assertFalse(booleanCaptor.value);
}
}
When I run the test, following exception is thrown:
java.lang.IllegalStateException: classCaptor.capture() must not be null
Is it possible to use argument captors in kotlin?
=========
UPDATE 1:
Kotlin: 1.0.0-beta-4584
Mockito: 1.10.19
Robolectric: 3.0
=========
UPDATE 2:
Stacktrace:
java.lang.IllegalStateException: classCaptor.capture() must not be null
at com.example.view.model.ShoplistsViewModelTest.thatNavigatesToAddListScreenOnAddClicked(ShoplistsViewModelTest.kt:92)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:251)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:188)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:54)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:152)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
From this blog
"Getting matchers to work with Kotlin can be a problem. If you have a method written in kotlin that does not take a nullable parameter then we cannot match with it using Mockito.any(). This is because it can return void and this is not assignable to a non-nullable parameter. If the method being matched is written in Java then I think that it will work as all Java objects are implicitly nullable."
A wrapper function is needed that returns ArgumentCaptor.capture() as nullable type.
Add the following as a helper method to your test
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
Please see, MockitoKotlinHelpers.kt provided by Google in the Android Architecture repo for reference. the capture function provides a convenient way to call ArgumentCaptor.capture(). Call
verify(activityHandlerMock).navigateTo(capture(classCaptor), capture(booleanCaptor))
Update: If the above solution does not work for you, please check Roberto Leinardi's solution in the comments below.
The return value of classCaptor.capture() is null, but the signature of IActivityHandler#navigateTo(Class, Boolean) does not allow a null argument.
The mockito-kotlin library provides supporting functions to solve this problem.
Code should be:
#Captor
lateinit var classCaptor: ArgumentCaptor<Class<BaseActivity>>
#Captor
lateinit var booleanCaptor: ArgumentCaptor<Boolean>
...
#Test
fun thatNavigatesToAddListScreenOnAddClicked(){
//given
//when
objectUnderTest?.addNewList()
//then
verify(activityHandlerMock).navigateTo(
com.nhaarman.mockitokotlin2.capture<Class<BaseActivity>>(classCaptor.capture()),
com.nhaarman.mockitokotlin2.capture<Boolean>(booleanCaptor.capture())
)
var clazzValue = classCaptor.value
assertNotNull(clazzValue);
val booleanValue = booleanCaptor.value
assertFalse(booleanValue);
}
OR
var classCaptor = com.nhaarman.mockitokotlin2.argumentCaptor<Class<BaseActivity>>()
var booleanCaptor = com.nhaarman.mockitokotlin2.argumentCaptor<Boolean>()
...
verify(activityHandlerMock).navigateTo(
classCaptor.capture(),
booleanCaptor.capture()
)
also in build.gradle add this:
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
Use kotlin-mockito https://mvnrepository.com/artifact/com.nhaarman/mockito-kotlin/1.5.0 as dependency and sample code as written below :
argumentCaptor<Hotel>().apply {
verify(hotelSaveService).save(capture())
assertThat(allValues.size).isEqualTo(1)
assertThat(firstValue.name).isEqualTo("İstanbul Hotel")
assertThat(firstValue.totalRoomCount).isEqualTo(10000L)
assertThat(firstValue.freeRoomCount).isEqualTo(5000L)
}
As stated by CoolMind in the comment, you first need to add gradle import for Kotlin-Mockito and then shift all your imports to use this library. Your imports will now look like:
import com.nhaarman.mockitokotlin2.argumentCaptor
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.eq
import com.nhaarman.mockitokotlin2.isNull
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
Then your test class will be something like this:
val mArgumentCaptor = argumentCaptor<SignUpInteractor.Callback>()
#Test
fun signUp_success() {
val customer = Customer().apply {
name = "Test Name"
email = "test#example.com"
phone = "0123444456789"
phoneDdi = "+92"
phoneNumber = ""
countryCode = "92"
password = "123456"
}
mPresenter.signUp(customer)
verify(mView).showProgress()
verify(mInteractor).createAccount(any(), isNull(), mArgumentCaptor.capture())
}
According this solution my solution here:
fun <T> uninitialized(): T = null as T
//open verificator
val verificator = verify(activityHandlerMock)
//capture (would be same with all matchers)
classCaptor.capture()
booleanCaptor.capture()
//hack
verificator.navigateTo(uninitialized(), uninitialized())
Came here after the kotlin-Mockito library didn't help.
I created a solution using reflection.
It is a function which extracts the argument provided to the mocked-object earlier:
fun <T: Any, S> getTheArgOfUsedFunctionInMockObject(mockedObject: Any, function: (T) -> S, clsOfArgument: Class<T>): T{
val argCaptor= ArgumentCaptor.forClass(clsOfArgument)
val ver = verify(mockedObject)
argCaptor.capture()
ver.javaClass.methods.first { it.name == function.reflect()!!.name }.invoke(ver, uninitialized())
return argCaptor.value
}
private fun <T> uninitialized(): T = null as T
Usage:
(Say I have mocked my repository and tested a viewModel. After calling the viewModel's "update()" method with a MenuObject object, I want to make sure that the MenuObject actually called upon the repository's "updateMenuObject()" method:
viewModel.update(menuObjectToUpdate)
val arg = getTheArgOfUsedFunctionInMockObject(mockedRepo, mockedRepo::updateMenuObject, MenuObject::class.java)
assertEquals(menuObjectToUpdate, arg)
You can write a wrapper over argument captor
class CaptorWrapper<T:Any>(private val captor:ArgumentCaptor<T>, private val obj:T){
fun capture():T{
captor.capture()
return obj
}
fun captor():ArgumentCaptor<T>{
return captor
}
}
Another approach:
/**
* Use instead of ArgumentMatcher.argThat(matcher: ArgumentMatcher<T>)
*/
fun <T> safeArgThat(matcher: ArgumentMatcher<T>): T {
ThreadSafeMockingProgress.mockingProgress().argumentMatcherStorage
.reportMatcher(matcher)
return uninitialized()
}
#Suppress("UNCHECKED_CAST")
private fun <T> uninitialized(): T = null as T
Usage:
verify(spiedElement, times(1)).method(
safeArgThat(
CustomMatcher()
)
)
If none of the fine solutions presented worked for you, here is one more way to try. It's based on Mockito-Kotlin.
[app/build.gradle]
dependencies {
...
testImplementation 'org.mockito.kotlin:mockito-kotlin:3.2.0'
}
Define Rule and Mock in your test file.
#RunWith(AndroidJUnit4::class)
class MockitoTest {
#get:Rule
val mockitoRule: MockitoRule = MockitoJUnit.rule()
#Mock
private lateinit var mockList: MutableList<String>
And here is an example.
#Test
fun `argument captor`() {
mockList.add("one")
mockList.add("two")
argumentCaptor<String>().apply {
// Verify that "add()" is called twice, and capture the arguments.
verify(mockList, times(2)).add(capture())
assertEquals(2, allValues.size)
assertEquals("one", firstValue)
assertEquals("two", secondValue)
}
}
}
Alternatively, you can also use #Captor as well.
#Captor
private lateinit var argumentCaptor: ArgumentCaptor<String>
#Test
fun `argument captor`() {
mockList.add("one")
mockList.add("two")
verify(mockList, times(2)).add(capture(argumentCaptor))
assertEquals(2, argumentCaptor.allValues.size)
assertEquals("one", argumentCaptor.firstValue)
assertEquals("two", argumentCaptor.secondValue)
}
Related
I am trying to write some Koin API tests, but they fail to execute because okhttp3 MockWebServer bean can not be found.
I am using:
Koin Core features: implementation "io.insert-koin:koin-core:3.2.0"
Koin Test features: testImplementation "io.insert-koin:koin-test:3.2.0"
These are my setup/teardown methods:
companion object {
const val TEST_SCOPE = "TEST_SCOPE"
}
#Before
fun initTest() {
stopKoin()
val modules = listOf(module {
scope(named(TEST_SCOPE)) { MockWebServer() }
factory {
AuthenticationManager(
get<MockWebServer>().url("").toString()
)
}
})
startKoin {
koinApplication { allowOverride(true) }
loadKoinModules(modules)
}
getKoin().createScope("AuthTestScope", named(TEST_SCOPE))
}
#After
fun shutdown() {
get<MockWebServer>().shutdown()
getKoin().getScope(TEST_SCOPE).close()
stopKoin()
}
And this is a test example:
#Test
fun `login sends proper body`() {
get<MockWebServer>().apply {
enqueue(MockResponse().setBody(MockResponseFileReader("auth_success.json").content))
}
val authManager = get<AuthenticationManager>().apply {
authenticateBlocking()
}
val testBody = LoginBody(AuthenticationManager.email, AuthenticationManager.password)
val requestBody =
URLDecoder.decode(get<MockWebServer>().takeRequest().body.readUtf8(), "UTF-8")
val requestParams = getParametersFromRequestBody(requestBody)
val requestEmail = requestParams[0]
val requestPassword = requestParams[1]
assertEquals(requestEmail, testBody.email)
assertEquals(requestPassword, testBody.password)
}
Tests themselves aren't that important since they break in the very first line when they try to get the MockWebServer.\
Full error log:
|- No definition found for class:'okhttp3.mockwebserver.MockWebServer'. Check your definitions!
org.koin.core.error.NoBeanDefFoundException: |- No definition found for class:'okhttp3.mockwebserver.MockWebServer'. Check your definitions!
at app//org.koin.core.scope.Scope.throwDefinitionNotFound(Scope.kt:305)
at app//org.koin.core.scope.Scope.resolveValue(Scope.kt:275)
at app//org.koin.core.scope.Scope.resolveInstance(Scope.kt:242)
at app//org.koin.core.scope.Scope.get(Scope.kt:205)
at app//com.riteh.autoshare.AuthenticationManagerTest.registration sends proper body(AuthenticationManagerTest.kt:178)
at java.base#11.0.12/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base#11.0.12/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base#11.0.12/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base#11.0.12/java.lang.reflect.Method.invoke(Method.java:566)
at app//org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at app//org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at app//org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at app//org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at app//org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at app//org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
at app//org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at app//org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at app//org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at app//org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at app//org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at app//org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at app//org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at app//org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at app//org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at app//org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at app//org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at app//org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:110)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:38)
at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:62)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51)
at java.base#11.0.12/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base#11.0.12/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base#11.0.12/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base#11.0.12/java.lang.reflect.Method.invoke(Method.java:566)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:176)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at app//worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Any help is appreciated. I guess this could be solved by switching to older Koin versions and some code refactoring, but I'm trying to see if there's a simpler solution that I'm missing.
Please Read the whole description I have tried my best to explain every corner of the code.
Mockito Says Actual invocation has different arguments
I am testing a update passwrod function which has a code like this ,
fun update() {
if (uiModel.validateData().isEmpty()) {
changePassword(
uiModel.oldpassword.value ?: "",
uiModel.newpassword.value ?: "",
uiModel.confirmpassword.value ?: ""
)
} else {
showToast(uiModel.validateData()[0])
}
}
uiModel.validateData().isEmpty() this line validate the user input if it is ok then it proceed inside of the block.
uiModel.oldpassword.value uiModel is my supporting class which has a Mutablelive data which is connected via databinding to EditTextView which gets and sets data into view. I have already mocked the uiModel class which is a simple class provided by dagger in viewModel constructor here is uiModel class code.
class ChangePasswordUiModel #Inject constructor() {
var oldpassword = MutableLiveData<String>().default("")
var newpassword= MutableLiveData<String>().default("")
var confirmpassword= MutableLiveData<String>().default("")
}
and here is the viewModel injection setup
#ChangePasswordScope
class ChangePasswordViewModel #Inject constructor(
private val useCase: ChangePasswordUseCase,
val uiModel: ChangePasswordUiModel
) : BaseFragmentViewModel() {
}
If validation is all set then this function gets called.
changePassword(uiModel.oldpassword.value ?: "",
uiModel.newpassword.value ?: "",
uiModel.confirmpassword.value ?: ""
)
which is actually looks like this.
var testValue = ""
private fun changePassword(oldpass: String, newpass: String, confirmPass: String) {
viewModelScope.launch {
useCase.changePassword(
oldpass,
newpass,
confirmPass
).let { result ->
when (result) {
is Result.Success -> {
testValue = "Success"
}
is Result.Exception -> showException(result.exception)
is Result.Error -> showError(parseError(result.errorBody))
else -> {
}
}
}
}
}
Now useCase.changePassword() this function actually does magic for me which actually initiates network request and return me a custom sealed class which has three values Success(Any()), Error(), Exception.
UseCase looks like this.
interface ChangePasswordUseCase {
suspend fun changePassword(oldpassword: String, newpassword: String, confirmpassword: String): Result
}
NOW THE PROBLEM WITH TESTING
I want to check after update() function invocation that changepassword is invoked or not
and my testing code looks like this,
// these are the values which set up in #before
val useCase = mock<ChangePasswordUseCase>()
val uiModel = mock<ChangePasswordUiModel>()
val SUT: ChangePasswordViewModel by lazy { ChangePasswordViewModel(useCase, uiModel) }
#Test
fun `update pass validate pass and change pass`() {
val emptyLiveData = MutableLiveData("abc")
whenever(uiModel.validateData()).thenReturn(mutableListOf())
whenever(uiModel.oldpassword).thenReturn(emptyLiveData)
whenever(uiModel.confirmpassword).thenReturn(emptyLiveData)
whenever(uiModel.newpassword).thenReturn(emptyLiveData)
runBlockingTest {
whenever(
useCase.changePassword(
(emptyLiveData.value!!),
(emptyLiveData.value!!),
(emptyLiveData.value!!)
)
).thenReturn(
Result.Success(
ChangePasswordResponse()
)
)
SUT.update()
verify(useCase).changePassword(
(emptyLiveData.value!!),
(emptyLiveData.value!!),
(emptyLiveData.value!!)
)
assertThat(SUT.testValue).isEqualTo("Success")
}
}
whenever is my extension function written on Mockito.when
and finally here is the problem which took my day but not solved ... I know the problem is values reference issue but I dont know how I solve this problem
ERROR
Argument(s) are different! Wanted:
changePasswordUseCase.changePassword(
"abc",
"abc",
"abc",
Continuation at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModelTest$update pass validate pass and change pass$1.invokeSuspend(ChangePasswordViewModelTest.kt:105)
);
-> at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModelTest$update pass validate pass and change pass$1.invokeSuspend(ChangePasswordViewModelTest.kt:102)
Actual invocation has different arguments:
changePasswordUseCase.changePassword(
"abc",
"abc",
"abc",
Continuation at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModel$changePassword$1.invokeSuspend(ChangePasswordViewModel.kt:61)
);
-> at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModel$changePassword$1.invokeSuspend(ChangePasswordViewModel.kt:58)
Comparison Failure:
<Click to see difference>
Argument(s) are different! Wanted:
changePasswordUseCase.changePassword(
"abc",
"abc",
"abc",
Continuation at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModelTest$update pass validate pass and change pass$1.invokeSuspend(ChangePasswordViewModelTest.kt:105)
);
-> at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModelTest$update pass validate pass and change pass$1.invokeSuspend(ChangePasswordViewModelTest.kt:102)
Actual invocation has different arguments:
changePasswordUseCase.changePassword(
"abc",
"abc",
"abc",
Continuation at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModel$changePassword$1.invokeSuspend(ChangePasswordViewModel.kt:61)
);
-> at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModel$changePassword$1.invokeSuspend(ChangePasswordViewModel.kt:58)
at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModelTest$update pass validate pass and change pass$1.invokeSuspend(ChangePasswordViewModelTest.kt:102)
at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModelTest$update pass validate pass and change pass$1.invoke(ChangePasswordViewModelTest.kt)
at kotlinx.coroutines.test.TestBuildersKt$runBlockingTest$deferred$1.invokeSuspend(TestBuilders.kt:50)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.test.TestCoroutineDispatcher.dispatch(TestCoroutineDispatcher.kt:50)
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:305)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:27)
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:109)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:158)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt:91)
at kotlinx.coroutines.BuildersKt.async(Unknown Source)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.async$default(Builders.common.kt:84)
at kotlinx.coroutines.BuildersKt.async$default(Unknown Source)
at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest(TestBuilders.kt:49)
at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest$default(TestBuilders.kt:45)
at com.trainerhub.trainer.view.home.view.change_password.ChangePasswordViewModelTest.update pass validate pass and change pass(ChangePasswordViewModelTest.kt:88)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:61)
at org.junit.rules.TestWatcher$1.evaluate(TestWatcher.java:61)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.CommandLineWrapper.main(CommandLineWrapper.java:64)
Process finished with exit code -1
everything looks perfect in your code.
when working with coroutines and suspend functions, you will need both coroutines test and mockito-kotlin dependencies.
make sure you have both dependencies added in your build.gralde.
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.2'
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
We upgraded our Auth0 Android library from 1.30 to 2.0 and some of our unit tests that use OkHttp's MockWebServer broke.
The first test fails with a java.lang.ExceptionInInitializerError which in turn is caused by java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked.
Next three tests fail with java.lang.NoClassDefFoundError: Could not initialize class com.auth0.android.request.internal.DefaultThreadSwitcher
The rest of the tests are stuck until cancelled.
For context, we use RxJava3 and Android Jetpack libs such as LiveData in our code base, and Retrofit for REST API.
I tried adding
#ExtendWith(InstantExecutorExtension::class, RxImmediateScheduler::class) in the class declaration like this:
#ExtendWith(InstantExecutorExtension::class, RxImmediateScheduler::class)
class AuthManagerTest {
But afterwards, all tests return with "Test ignored" in the logs:
Test ignored.
java.lang.InstantiationException
at sun.reflect.InstantiationExceptionConstructorAccessorImpl.newInstance(InstantiationExceptionConstructorAccessorImpl.java:48)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.junit.platform.commons.util.ReflectionUtils.newInstance(ReflectionUtils.java:513)
at org.junit.platform.commons.util.ReflectionUtils.newInstance(ReflectionUtils.java:488)
at org.junit.jupiter.engine.extension.MutableExtensionRegistry.registerExtension(MutableExtensionRegistry.java:176)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at org.junit.jupiter.engine.extension.MutableExtensionRegistry.createRegistryFrom(MutableExtensionRegistry.java:117)
at org.junit.jupiter.engine.descriptor.ExtensionUtils.populateNewExtensionRegistryFromExtendWithAnnotation(ExtensionUtils.java:77)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.prepare(ClassBasedTestDescriptor.java:143)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.prepare(ClassBasedTestDescriptor.java:78)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$prepare$1(NodeTestTask.java:111)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.prepare(NodeTestTask.java:111)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:79)
at java.util.ArrayList.forEach(ArrayList.java:1257)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Here is a code snippet on how we initialize the test class:
class AuthManagerTest {
...
private lateinit var authManager: AuthManager
private lateinit var config: Auth0Config
private lateinit var server: MockWebServer
#MockK
private lateinit var context: Context
#MockK
private lateinit var credentialsManager: SecureCredentialsManager
...
...
#BeforeEach
fun setup() {
MockKAnnotations.init(this)
every { context.getSharedPreferences(any(), any()) } returns mockk()
every { credentialsManager.authToken } returns null
every { credentialsManager.userId } returns null
every { credentialsManager.refreshToken } returns null
every { credentialsManager.saveCredentials(any()) } just Runs
...
mockkObject(Credentials.Companion)
every { Credentials.fromAuth0Creds(any(), any()) } returns mockk()
server = MockWebServer().apply {
// we didn't use useHttps before but with the new version of Auth0 it seems https is mandatory
useHttps(testSslSocketFactory(), false)
start()
}
...
}
Here is the first test that fails:
#Test
fun `sendEmailCode returns EmailSent when response is successful`() {
server.enqueue(mockResponse(200, email_success_response))
val emailResult = authManager.sendEmailCode("me#example.org").blockingGet()
as EmailResult.EmailSent
assertEquals("me#example.org", emailResult.email)
assertNoToken()
}
Already fixed with version 2.3.0 of Auth0 android lib--see the README on how to solve this issue here
in Android I've been using the version 1.6.1 of Powermock and all this implementation worked really good for statics.
It's not working now at all when I changed to 2.0.0-beta.5. Indeed, it didn't even work upgrading from my previous 1.6.1 to 1.7.1.
I have this implementation:
// Power Mockito
testImplementation "org.powermock:powermock-api-mockito2:2.0.0-beta.5"
testImplementation "org.powermock:powermock-module-junit4-rule-agent:2.0.0-beta.5"
testImplementation "org.powermock:powermock-module-junit4:2.0.0-beta.5"
//testImplementation 'org.powermock:powermock-module-junit4-rule:2.0.0-beta.5'
// Mockito
testImplementation "org.mockito:mockito-core:2.11.0"
testImplementation "com.nhaarman:mockito-kotlin-kt1.1:1.5.0"
androidTestImplementation("com.nhaarman:mockito-kotlin-kt1.1:1.5.0", {
exclude group: 'org.mockito', module: 'mockito-core'
})
androidTestImplementation 'org.mockito:mockito-android:2.11.0'
and I'm trying to mock a static the same way I was doing with 1.6.1:
#RunWith(PowerMockRunner::class)
#PrepareForTest(SharedPreferencesHelper.Companion::class, ConfigUseCaseTests::class)
class ConfigUseCaseTests {
lateinit var context: Context
#Before
fun setUp() {
context = mock()
}
#Test
fun getConfigs_fromJson() {
PowerMockito.mockStatic(SharedPreferencesHelper.Companion::class.java)
val instance = mock<SharedPreferencesHelper.Companion>()
doReturn("foo")
.whenever(instance)
.loadString(isA(), anyString(), anyString(), anyString())
// whenever(instance.loadString(isA(), anyString(), anyString(), anyString())).thenReturn("foo") // This shows the same error
PowerMockito.whenNew(SharedPreferencesHelper.Companion::class.java)
.withAnyArguments()
.thenReturn(instance)
val mockedFoo = instance.loadString(context, "", "", "") // This shows "foo"
val mockedCompanion = SharedPreferencesHelper.loadString(context, "", "", "") // This is throwing NullPointerException
Assert.assertEquals(mockedCompanion, "foo")
}
}
My SharedPreferencesHelper looks like:
class SharedPreferencesHelper {
companion object {
#Suppress("NON_FINAL_MEMBER_IN_OBJECT")
open fun loadString(context: Context, fileName: String, key: String, defaultValue: String?): String? {
val sharedPreferences = getWithFileName(context, fileName)
return if (!sharedPreferences.contains(key)) {
defaultValue
} else {
sharedPreferences.getString(key, defaultValue)
}
}
}
}
I've tried to play with the open but it didn't work.
Exception: (I don't understand it at all)
java.lang.NullPointerException
at my.package.ConfigUseCaseTests.getConfigs_fromJson(ConfigUseCaseTests.kt:45)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:68)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:326)
at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:89)
at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:97)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:310)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTestInSuper(PowerMockJUnit47RunnerDelegateImpl.java:131)
I can say, sometimes IT WORKS!! I'm adding the video because it looks amazing that it happens just sometimes:
https://youtu.be/YZObVLcERBo (watch at the middle and the end)
The way companion objects are created on compilation is by creating a static field inside the surrounding class. It get's instantiated on the static scope (before the test is instantiated).
This is how it looks when decompiled in Java:
public final class SharedPreferencesHelper {
public static final SharedPreferencesHelper.Companion Companion = new
SharedPreferencesHelper.Companion((DefaultConstructorMarker)null);
// ...
}
For this to work you'll have to assign the given field with your mock instead of intercepting the creation of the Companion object. This doesn't even require to use PowerMock and can be done with reflexion: https://dzone.com/articles/how-to-change-private-static-final-fields
I try to mock some methods in the project so that when they are called, a certain value is returned.
But when you run the tests, they fall with the output:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers! 0 matchers expected, 1 recorded:
-> at com.hodzi.stackviewer.questions.detail.QuestionDetailPresenterTest.voteTest(QuestionDetailPresenterTest.kt:69)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), "raw String"); When using matchers, all arguments have to be provided by matchers. For example:
//correct:
someMethod(anyObject(), eq("String by matcher"));
If you run the same code in debug mode and run through all the lines, then when you call shared.getToken (), the value that we specified is returned. But with normal startup, tests fall on this line.
Code:
import com.hodzi.stackviewer.questions.QuestionsInteractor
import com.hodzi.stackviewer.utils.Shared
import com.hodzi.stackviewer.utils.Vote
import org.junit.BeforeClass
import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.Mockito
internal class QuestionDetailPresenterTest {
companion object {
lateinit var presenter: QuestionDetailPresenter
lateinit var view: QuestionDetailView
#BeforeClass #JvmStatic
fun setUp() {
val questionsInteractor: QuestionsInteractor =
Mockito.mock(QuestionsInteractor::class.java)
val shared: Shared =
Mockito.mock(Shared::class.java)
Mockito.`when`(shared.getToken()).thenReturn("23")
// Mockito.doReturn("23").`when`(shared).getToken()
view = Mockito.mock(QuestionDetailView::class.java)
presenter = QuestionDetailPresenter(questionsInteractor, shared)
}
}
#Test
fun voteTest() {
presenter.vote(ArgumentMatchers.anyInt(), Vote.QUESTION_DOWN)
Mockito.verify(view).goToAuth()
}
}
Shared:
interface Shared {
companion object {
const val KEY_TOKEN: String = "keyToken"
}
fun getToken(): String
fun saveToken(token: String?)
}
Presenter:
class QuestionDetailPresenter(val questionsInteractor: QuestionsInteractor, val shared: Shared) :
BasePresenter<QuestionDetailView>() {
lateinit var question: Question
fun vote(id: Int, vote: Vote) {
print(vote)
if (Strings.isEmptyString(shared.getToken())) {
view?.goToAuth()
return
}
val observable: Observable<out Data> = when (vote) {
Vote.ANSWER_UP -> {
questionsInteractor.answerUpVote(id, shared.getToken())
}
Vote.ANSWER_DOWN -> {
questionsInteractor.answerDownVote(id, shared.getToken())
}
Vote.QUESTION_UP -> {
questionsInteractor.questionUpVote(id, shared.getToken())
}
Vote.QUESTION_DOWN -> {
questionsInteractor.questionDownVote(id, shared.getToken())
}
}
baseObservableData(observable,
{ data ->
run {
Log.d(Const.LOG_TAG, "success")
}
},
{ throwable ->
run {
Log.d(Const.LOG_TAG, "error")
}
}
)
}
}
Thanks!
There's nothing wrong with your mocking of shared, I think the problem is with:
presenter.vote(ArgumentMatchers.anyInt(), Vote.QUESTION_DOWN)
Just use a real Int instead of the ArgumentMatchers.anyInt().
Like
presenter.vote(0, Vote.QUESTION_DOWN)
Matchers are used when matching arguments on a mocked object, for example
val calulator = (mock with Mockito)
when(calculator.divideByTwo(anyInt()).thenReturn(1)
would mean calculator.divideByTwo(int: Int) returns 1 when called with any Int.
When calling methods of real objects to test them (like you do with your presenter), you use real parameters.