mockk icon indicating copy to clipboard operation
mockk copied to clipboard

any() doesn't work for value classes

Open maciejtulaza opened this issue 2 years ago • 5 comments

Prerequisites

Please answer the following questions for yourself before submitting an issue.

  • [x] I am running the latest version
  • [x] I checked the documentation and found no answer
  • [x] I checked to make sure that this issue has not already been filed

Expected Behavior

The mock works and returns expected value from method

Current Behavior

Test fails with NullPointerException from io.mockk.impl.recording.states.RecordingState.matcher(RecordingState.kt:53)

Failure Information (for bugs)

After running the test where I use any() for value class I get NPE. I did not find any info about using value classes in MockK in your docs. However I've found similar issue reported in Mockito's GitHub: https://github.com/mockito/mockito-kotlin/issues/445

Steps to Reproduce

Create such classes:

@JvmInline
value class Password(val pass: String)

interface SomeService {
    fun someMethod(password: Password): Int
}

class BasicSomeService : SomeService {
    override fun someMethod(password: Password): Int = 1234
}

and then one test:

class MockKFailTest : StringSpec({
    "mockk cant handle value classes" {
        val mock = mockk<SomeService>()
        every { mock.someMethod(any()) } returns 4321

        val resp = mock.someMethod(Password("qwerty"))

        resp shouldBe 4321
    }
})

Just run and you should get NPE.

Context

  • MockK version: 1.12.4
  • Kotest version: 5.3.0
  • OS: MacOS 11.6 BigSur
  • Kotlin version: 1.6.21
  • JDK version: 17.0.2-open
  • Type of test: unit test

Failure Logs

The output of such test is as follows:

java.lang.NullPointerException
at io.mockk.impl.recording.states.RecordingState.matcher(RecordingState.kt:53)
at io.mockk.impl.recording.CommonCallRecorder.matcher(CommonCallRecorder.kt:52)

maciejtulaza avatar Jul 14 '22 05:07 maciejtulaza

See here: https://github.com/mockk/mockk/issues/152

There's a workaround in this comment: https://github.com/mockk/mockk/issues/152#issuecomment-1189503722

aSemy avatar Jul 20 '22 16:07 aSemy

Thanks @aSemy Indeed part of this problem had been fixed by abovementioned issue

However, I still notice very strange behaviour of any() for value classes.

First problem:

Consider following setup:

@JvmInline
value class Foo(private val value: UUID)

@JvmInline
value class Bar(private val value: BigDecimal) {
    override fun toString(): String = value.toPlainString()
}

data class Wrapper(
    val bar: Bar,
)

class Service {
    fun method(wrapper: Wrapper, foo: Foo) {}
}

class AnyDoesntWork : StringSpec({
    "mocking fails on BigDecimal.toPlainString()" {
        val mock = mockk<Service>()

        verify(exactly = 0) { mock.method(any(), any()) }
    }
})

this will fail with an exception:

Cannot invoke "java.math.BigDecimal.toPlainString()" because "arg0" is null
java.lang.NullPointerException: Cannot invoke "java.math.BigDecimal.toPlainString()" because "arg0" is null
	at com.project.something.Bar.toString-impl(AnyDoesntWork.kt:16)
	at com.project.something.Wrapper.toString(AnyDoesntWork.kt)
	at java.base/java.lang.String.valueOf(String.java:4215)
	at java.base/java.lang.StringBuilder.append(StringBuilder.java:173)
	at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457)
	at java.base/java.lang.String.valueOf(String.java:4215)
	at java.base/java.lang.StringBuilder.append(StringBuilder.java:173)

I don't know what exactly is causing this problem, but I noticed to get this exception you have to have:

  1. Two arguments in Service's method (order doesn't matter)
  2. One argument has to be a data class that contains property which is value class for BigDecimal (for non-data class another error is thrown: "Failed matching mocking signature for...")
  3. Second argument has to be a value class for type UUID

If you change any of these, the error will not appear.

Second problem:

Also, there is another problem I encountered. Trying to do simple verify {} does not work with value class, see:

class Service {
    fun method(foo: Foo) {}
}

class AnyDoesntWork : StringSpec({
    "mocking doesnt work" {
        val mock = mockk<Service>()

        verify(exactly = 0) { mock.method(any()) }
    }
})

this throws an exception:

Failed matching mocking signature for
SignedCall(retValue=, isRetValueMock=true, retType=class kotlin.Unit, self=Service(#1), method=method-X8XA9MQ(UUID), args=[00000000-0000-0000-0000-000000000000], invocationStr=Service(#1).method-X8XA9MQ(00000000-0000-0000-0000-000000000000))
left matchers: [any()]
io.mockk.MockKException: Failed matching mocking signature for
SignedCall(retValue=, isRetValueMock=true, retType=class kotlin.Unit, self=Service(#1), method=method-X8XA9MQ(UUID), args=[00000000-0000-0000-0000-000000000000], invocationStr=Service(#1).method-X8XA9MQ(00000000-0000-0000-0000-000000000000))
left matchers: [any()]
	at app//io.mockk.impl.recording.SignatureMatcherDetector.detect(SignatureMatcherDetector.kt:99)
	at app//io.mockk.impl.recording.states.RecordingState.signMatchers(RecordingState.kt:39)
	at app//io.mockk.impl.recording.states.RecordingState.round(RecordingState.kt:31)
	at app//io.mockk.impl.recording.CommonCallRecorder.round(CommonCallRecorder.kt:50)
	at app//io.mockk.impl.eval.RecordedBlockEvaluator.record(RecordedBlockEvaluator.kt:63)

I'm using Kotest 5.4.0 and MockK 1.12.5

maciejtulaza avatar Jul 30 '22 10:07 maciejtulaza

Yeah - nested value classes aren't supported at present #859

aSemy avatar Jul 30 '22 11:07 aSemy

@aSemy right! and what about the second issue?

maciejtulaza avatar Jul 30 '22 12:07 maciejtulaza

might be related to to my comment https://github.com/mockk/mockk/pull/849#issuecomment-1200168219

qoomon avatar Jul 30 '22 14:07 qoomon

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. If you are sure that this issue is important and should not be marked as stale just ask to put an important label.

stale[bot] avatar Nov 02 '22 02:11 stale[bot]