Skip to content

Commit cd674c4

Browse files
authored
Add value class support to eq() argument matcher (#545)
Fixes #544
1 parent 4449126 commit cd674c4

File tree

3 files changed

+70
-5
lines changed

3 files changed

+70
-5
lines changed

mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Matchers.kt

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ import org.mockito.kotlin.internal.createInstance
3131
import kotlin.reflect.KClass
3232

3333
/** Object argument that is equal to the given value. */
34-
fun <T> eq(value: T): T {
34+
inline fun <reified T : Any?> eq(value: T): T {
35+
if(T::class.isValue)
36+
return eqValueClass(value)
37+
3538
return ArgumentMatchers.eq(value) ?: value
3639
}
3740

@@ -80,15 +83,30 @@ inline fun <reified T > anyValueClass(): T {
8083
"${T::class.qualifiedName} is not a value class."
8184
}
8285

83-
val boxImpls = T::class.java.declaredMethods.filter { it.name == "box-impl" && it.parameterCount == 1 }
84-
require(boxImpls.size == 1) // Sanity check
85-
86-
val boxImpl = boxImpls.first()
86+
val boxImpl =
87+
T::class.java.declaredMethods
88+
.single { it.name == "box-impl" && it.parameterCount == 1 }
8789
val boxedType = boxImpl.parameters[0].type
8890

8991
return boxImpl.invoke(null, ArgumentMatchers.any(boxedType)) as T
9092
}
9193

94+
inline fun <reified T > eqValueClass(value: T): T {
95+
require(T::class.isValue) {
96+
"${T::class.qualifiedName} is not a value class."
97+
}
98+
99+
val unboxImpl =
100+
T::class.java.declaredMethods
101+
.single { it.name == "unbox-impl" && it.parameterCount == 0 }
102+
val unboxed = unboxImpl.invoke(value)
103+
104+
val boxImpl =
105+
T::class.java.declaredMethods.single { it.name == "box-impl" && it.parameterCount == 1 }
106+
107+
return boxImpl.invoke(null, ArgumentMatchers.eq(unboxed) ?: unboxed) as T
108+
}
109+
92110
/**
93111
* Creates a custom argument matcher.
94112
* `null` values will never evaluate to `true`.

tests/src/test/kotlin/test/EqTest.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,18 @@ class EqTest : TestBase() {
9494
expect(result).toBeNull()
9595
}
9696

97+
@Test
98+
fun eqValueClassInstance() {
99+
/* Given */
100+
val valueClass = ValueClass("Content")
101+
102+
/* When */
103+
val result = eq(valueClass)
104+
105+
/* Then */
106+
expect(result).toBe(valueClass)
107+
}
108+
97109
private interface MyInterface
98110
private open class MyClass : MyInterface
99111
class ClosedClass

tests/src/test/kotlin/test/MatchersTest.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,41 @@ class MatchersTest : TestBase() {
389389
}
390390
}
391391

392+
@Test
393+
fun eq_forValueClass() {
394+
val valueClass = ValueClass("Content")
395+
mock<Methods>().apply {
396+
valueClass(valueClass)
397+
verify(this).valueClass(eq(valueClass))
398+
}
399+
}
400+
401+
@Test
402+
fun eq_withNestedValueClass() {
403+
val nestedValueClass = NestedValueClass(ValueClass("Content"))
404+
mock<Methods>().apply {
405+
nestedValueClass(nestedValueClass)
406+
verify(this).nestedValueClass(eq(nestedValueClass))
407+
}
408+
}
409+
410+
@Test
411+
fun eq_withClosedClass() {
412+
val closedClassInstance = Closed()
413+
mock<Methods>().apply {
414+
closed(closedClassInstance)
415+
verify(this).closed(eq(closedClassInstance))
416+
}
417+
}
418+
419+
@Test
420+
fun eq_withInt() {
421+
mock<Methods>().apply {
422+
int(3)
423+
verify(this).int(eq(3))
424+
}
425+
}
426+
392427
/**
393428
* a VarargMatcher implementation for varargs of type [T] that will answer with type [R] if any of the var args
394429
* matched. Needs to keep state between matching invocations.

0 commit comments

Comments
 (0)