I work on a code basis written in Kotlin that uses jOOQ to interact with databases. It uses jOOQ to generate the "meta model" i.e. classes abstracting tables, fields, etc. jOOQ generates those classes in Java. Our unit tests mock some of the jOOQ classes using MockK, a Kotlin mocking library.
Here's a simplified example.
@Test
fun `should throw exception for nonexistent fields`() {
// given
val table = mockk<TableImpl<*>>()
every { table.field(any<String>()) } returns null
every { table.name } returns "table-name"
// when
// then
}
After upgrading from jOOQ 3.14 to 3.18 MockK fails to mock jOOQ classes with an error message like below.
[io.moc.imp.JvmMockKGateway] (main) Starting JVM MockK implementation. Java version = 11.0.18.
[io.moc.pro.jvm.JvmMockKAgentFactory] (main) Byte buddy agent installed
[io.moc.pro.jvm.dis.BootJarLoader] (main) Bootstrap class loaded io.mockk.proxy.jvm.dispatcher.JvmMockKDispatcher
[io.moc.pro.jvm.dis.BootJarLoader] (main) Bootstrap class loaded io.mockk.proxy.jvm.dispatcher.JvmMockKWeakMap
[io.moc.pro.jvm.dis.BootJarLoader] (main) Bootstrap class loaded io.mockk.proxy.jvm.dispatcher.JvmMockKWeakMap$StrongKey
[io.moc.pro.jvm.dis.BootJarLoader] (main) Bootstrap class loaded io.mockk.proxy.jvm.dispatcher.JvmMockKWeakMap$WeakKey
[io.moc.imp.ins.AbstractMockFactory] (main) Creating mockk for TableImpl name=#1
[io.moc.imp.ins.AbstractMockFactory] (main) Building proxy for TableImpl hashcode=58a4a74d
[io.moc.pro.jvm.tra.JvmInlineInstrumentation] (main) Retransforming [Ljava.lang.Class;@6f3f0fae
[io.moc.pro.jvm.tra.JvmInlineInstrumentation] (main) Failed to transform classes [
class org.jooq.impl.TableImpl,
interface org.jooq.impl.ScopeMappable,
interface org.jooq.QueryPartInternal,
interface org.jooq.QueryPart,
interface java.io.Serializable,
interface org.jooq.impl.ScopeNestable,
interface org.jooq.impl.SimpleCheckQueryPart,
interface org.jooq.impl.QOM$UNotYetImplemented,
interface org.jooq.impl.QOM$UEmpty,
class org.jooq.impl.AbstractTable,
interface org.jooq.Table,
interface org.jooq.TableLike,
interface org.jooq.Fields,
interface org.jooq.RecordQualifier,
interface org.jooq.Qualified,
interface org.jooq.Named,
interface org.jooq.GroupField,
interface org.jooq.SelectField,
interface org.jooq.SelectFieldOrAsterisk,
interface org.jooq.Typed,
interface org.jooq.impl.FieldsTrait,
interface org.jooq.impl.QOM$Aliasable,
class org.jooq.impl.AbstractNamed,
class org.jooq.impl.AbstractQueryPart,
class java.lang.Object
]: java.lang.UnsupportedOperationException: class redefinition failed: attempted to add a method
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.RetransformInlineInstrumentation$execute$1.invoke(RetransformInlineInstrumentation.kt:19)
at io.mockk.proxy.common.transformation.RetransformInlineInstrumentation$execute$1.invoke(RetransformInlineInstrumentation.kt:16)
at io.mockk.proxy.common.transformation.ClassTransformationSpecMap.applyTransformation(ClassTransformationSpecMap.kt:41)
at io.mockk.proxy.common.transformation.RetransformInlineInstrumentation.execute(RetransformInlineInstrumentation.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:24)
at io.mockk.impl.instantiation.AbstractMockFactory.mockk(AbstractMockFactory.kt:59)
at my-test-here
The jOOQ code must have changed in a way that prevents MockK to alter those classes to create mocks/proxies thereof. I haven't been able yet to figure what modification exactly MockK is trying to carry out on the jOOQ classes - and the error message doesn't say. "adding a method", yes, but which one? Where?
There are a couple of similar issues on the MockK issue tracker, some open, others closed, but so far it largely remains a mystery.
https://github.com/mockk/mockk/issues?q=is%3Aissue+class+redefinition+failed+is%3Aopen
Also, I looked into the jOOQ TableImpl
changes between 3.14 and 3.18 and found nothing that sticks out.
Update
I built a reproducer at https://github.com/marcelstoer/jooq-mockk-test. It appears the jOOQ 3.14.16 -> 3.15 update broke its use with MockK.
An interesting hint is hidden the MockK trace log messages.
jOOQ 3.14.16
...
[io.mockk.impl.instantiation.AbstractMockFactory] (main) Creating mockk for TableImpl name=#1
[io.mockk.impl.instantiation.AbstractMockFactory] (main) Building proxy for TableImpl hashcode=290b1b2e
[io.mockk.proxy.jvm.transformation.JvmInlineInstrumentation] (main) Retransforming [Ljava.lang.Class;@7d755813
[io.mockk.proxy.common.ProxyMaker] (main) Taking instance of class org.jooq.impl.TableImpl itself because it is not abstract and no additional interfaces specified.
[io.mockk.proxy.common.ProxyMaker] (main) Instantiating proxy for class org.jooq.impl.TableImpl via instantiator
...
jOOQ 3.15.0
[io.mockk.impl.instantiation.AbstractMockFactory] (main) Creating mockk for TableImpl name=#1
[io.mockk.impl.instantiation.AbstractMockFactory] (main) Building proxy for TableImpl hashcode=6fff253c
[io.mockk.proxy.jvm.transformation.JvmInlineInstrumentation] (main) Retransforming [Ljava.lang.Class;@72fe8a4f
[io.mockk.proxy.jvm.transformation.JvmInlineInstrumentation] (main) Failed to transform classes
So, with jOOQ 3.14.16 MockK doesn't actually (need to) build a proxy for TableImpl
because it
is not abstract and no additional interfaces specified
=> now filed under https://github.com/mockk/mockk/issues/1118