Kotlin / kotlinx-kover

Apache License 2.0
1.28k stars 48 forks source link

Kover breaks mocking OkHttp's Response with MockK #88

Closed cristan closed 8 months ago

cristan commented 2 years ago

Tested on Kover 0.4.4:

plugins {
    id 'org.jetbrains.kotlinx.kover' version '0.4.4'
}

Dependencies:

implementation 'com.squareup.okhttp3:okhttp:4.9.1'
testImplementation "io.mockk:mockk:1.12.1"

Tests:

@Test
fun testMockingRequest() {
    val response: Request = mockk()
}

@Test
fun testMockingResponse() {
    val response: Response = mockk()
}

testMockingResponse() fails here with this stacktrace:

java.lang.VerifyError
    at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
    at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:167)
    at io.mockk.proxy.jvm.transformation.JvmInlineInstrumentation.retransform(JvmInlineInstrumentation.kt:28)
    at io.mockk.proxy.common.transformation.RetransformInlineInstrumnetation$execute$1.invoke(RetransformInlineInstrumnetation.kt:19)
    at io.mockk.proxy.common.transformation.RetransformInlineInstrumnetation$execute$1.invoke(RetransformInlineInstrumnetation.kt:6)
    at io.mockk.proxy.common.transformation.ClassTransformationSpecMap.applyTransformation(ClassTransformationSpecMap.kt:41)
    at io.mockk.proxy.common.transformation.RetransformInlineInstrumnetation.execute(RetransformInlineInstrumnetation.kt:16)
    at io.mockk.proxy.jvm.ProxyMaker.inline(ProxyMaker.kt:88)
    at io.mockk.proxy.jvm.ProxyMaker.proxy(ProxyMaker.kt:30)
    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.mockk(AbstractMockFactory.kt:59)
    at com.example.testkoverandroidproject.ExampleUnitTest.testMockingResponse(ExampleUnitTest.kt:73)

This doesn't happen when I don't add Kover as a plugin. Surprisingly enough, testMockingRequest() always succeeds, so there's something in in mocking Response which goes wrong which doesn't happen when mocking Request.

shanshin commented 2 years ago

Hi, to eliminate the conflict between the MockK and the Kover, try to add all OkHttp classes to the instrumentation exclusions

    testOptions {
        unitTests.all {
            kover {
                excludes = ['okhttp3\\..+']
            }
        }
    }
cristan commented 2 years ago

It's a little weird that this is needed, but excluding OkHttp classes indeed works.

cristan commented 2 years ago

Another instance where mocking okhttp stuff is broken by kover:

@Test
fun `test mocking proceeding a chain`() {
    val testChain = mockk<Interceptor.Chain>()
    val argument = slot<Request>()
    every { testChain.proceed(capture(argument)) }.returns(mockk())
}

This gives this error:

java.lang.VerifyError
    at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
    at java.instrument/sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:167)
    at io.mockk.proxy.jvm.transformation.JvmInlineInstrumentation.retransform(JvmInlineInstrumentation.kt:28)

Even excluding all okhttp classes doesn't resolve this issue (tested on Kover 0.5.0-RC2).

foo4u commented 2 years ago

@shanshin, your recommended workaround isn't working with Kover 0.5.0. After trying different patterns, I switched the coverage engine to JACOCO and it works. The issue seems to be with the kotlinx.kover.api.CoverageEngine.INTELLIJ, which is the default.

Here's all I added to my build.gradle.kts that fixed it:

kover {
    coverageEngine.set(kotlinx.kover.api.CoverageEngine.JACOCO)
}

Does swapping the coverage engine basically make Kover no more useful than using JACOCO directly?

shanshin commented 2 years ago

@foo4u, in version 0.5.0, the format of filters for classes has changed. Now, instead of specifying a regular expression, you can use only wildcards * and ?. Therefore, now you can exclude OkHttp from instrumentation like this:

    testOptions {
        unitTests.all {
            kover {
                excludes = ['okhttp3.*']
            }
        }
    }
louiseightsix commented 2 years ago

Thanks for providing a workaround; this is affecting us as well - would love to get to the bottom of it.

wezley98 commented 2 years ago

Can confirm this also happens to us with the following config on 0.5.1

kover {
    jacocoEngineVersion.set("0.8.8")
    generateReportOnCheck = true
    disabledProjects = setOf(":di", ":icons", ":test")
    instrumentAndroidPackage = false
    runAllTestsForProjectTask = false
}

Can confirm workaround is still working for 0.5.1. Kotlin DSL fix, for anybody not using groovy:


android {
......
testOptions {
        unitTests.all {
            it.extensions.configure(kotlinx.kover.api.KoverTaskExtension::class) {
                excludes = listOf("okhttp3.*")
            }
        }
    }
}
shanshin commented 8 months ago

There are two ways to avoid this problem:

shanshin commented 8 months ago

Closed in favor of #418