Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mockk spky throwing NoClassDefFoundError

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?

like image 769
Mehdi Satei Avatar asked May 28 '21 09:05

Mehdi Satei


People also ask

What is mocking behavior of a mock?

Mocking behavior of such a mock is connected to the special prototype mock denoted by anyConstructed<MockCls> (). There is one instance per class of such a prototype mock. Call recording also happens to the prototype mock.

Where can I find the documentation for mockk?

Check the series of articles “Mocking is not rocket science” at Kt. Academy describing MockK from the very basics of mocking up to description of all advanced features. springmockk introduced in official Spring Boot Kotlin tutorial quarkus-mockk adds support for mocking beans in Quarkus. Documentation can be found here

How many instances of a prototype mock are there per class?

There is one instance per class of such a prototype mock. Call recording also happens to the prototype mock. If no behavior for the function is specified, then the original function is executed. In case a class has more than one constructor, each can be mocked separately:

What is the difference between a relaxed mock and a mock?

Spies allow you to mix mocks and real objects. Note: the spy object is a copy of the passed object. A relaxed mock is the mock that returns some simple value for all functions. This allows you to skip specifying behavior for each case, while still stubbing things you need. For reference types, chained mocks are returned.


1 Answers

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',
            ]
        }
    }
}
like image 64
vvbYWf0ugJOGNA3ACVxp Avatar answered Oct 21 '22 01:10

vvbYWf0ugJOGNA3ACVxp