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
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.
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',
]
}
}
}
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.
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 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)
}