1

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

Marcel Stör
  • 22,695
  • 19
  • 92
  • 198
  • You can set a system property net.bytebuddy.dump=/some/folder to get the processed class files written out. You can then use javap to compare signatures. – Rafael Winterhalter Jul 12 '23 at 20:10
  • Thanks. In the issue I filed, one of the maintainers is hinting at MockK maybe using your ByteBuddy incorrectly. – Marcel Stör Jul 13 '23 at 13:51

0 Answers0