mockk
mockk copied to clipboard
any() doesn't work for value classes
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)
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
Thanks @aSemy Indeed part of this problem had been fixed by abovementioned issue
However, I still notice very strange behaviour of any()
for value class
es.
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:
- Two arguments in
Service
's method (order doesn't matter) - 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...") - 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
Yeah - nested value classes aren't supported at present #859
@aSemy right! and what about the second issue?
might be related to to my comment https://github.com/mockk/mockk/pull/849#issuecomment-1200168219
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.