diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Matchers.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Matchers.kt index 48bdb0c..e276c41 100644 --- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Matchers.kt +++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Matchers.kt @@ -31,7 +31,10 @@ import org.mockito.kotlin.internal.createInstance import kotlin.reflect.KClass /** Object argument that is equal to the given value. */ -fun eq(value: T): T { +inline fun eq(value: T): T { + if(T::class.isValue) + return eqValueClass(value) + return ArgumentMatchers.eq(value) ?: value } @@ -80,15 +83,30 @@ inline fun anyValueClass(): T { "${T::class.qualifiedName} is not a value class." } - val boxImpls = T::class.java.declaredMethods.filter { it.name == "box-impl" && it.parameterCount == 1 } - require(boxImpls.size == 1) // Sanity check - - val boxImpl = boxImpls.first() + val boxImpl = + T::class.java.declaredMethods + .single { it.name == "box-impl" && it.parameterCount == 1 } val boxedType = boxImpl.parameters[0].type return boxImpl.invoke(null, ArgumentMatchers.any(boxedType)) as T } +inline fun eqValueClass(value: T): T { + require(T::class.isValue) { + "${T::class.qualifiedName} is not a value class." + } + + val unboxImpl = + T::class.java.declaredMethods + .single { it.name == "unbox-impl" && it.parameterCount == 0 } + val unboxed = unboxImpl.invoke(value) + + val boxImpl = + T::class.java.declaredMethods.single { it.name == "box-impl" && it.parameterCount == 1 } + + return boxImpl.invoke(null, ArgumentMatchers.eq(unboxed) ?: unboxed) as T +} + /** * Creates a custom argument matcher. * `null` values will never evaluate to `true`. diff --git a/tests/src/test/kotlin/test/EqTest.kt b/tests/src/test/kotlin/test/EqTest.kt index 2f64c09..3c55ee4 100644 --- a/tests/src/test/kotlin/test/EqTest.kt +++ b/tests/src/test/kotlin/test/EqTest.kt @@ -94,6 +94,18 @@ class EqTest : TestBase() { expect(result).toBeNull() } + @Test + fun eqValueClassInstance() { + /* Given */ + val valueClass = ValueClass("Content") + + /* When */ + val result = eq(valueClass) + + /* Then */ + expect(result).toBe(valueClass) + } + private interface MyInterface private open class MyClass : MyInterface class ClosedClass diff --git a/tests/src/test/kotlin/test/MatchersTest.kt b/tests/src/test/kotlin/test/MatchersTest.kt index f1c1382..341234a 100644 --- a/tests/src/test/kotlin/test/MatchersTest.kt +++ b/tests/src/test/kotlin/test/MatchersTest.kt @@ -389,6 +389,41 @@ class MatchersTest : TestBase() { } } + @Test + fun eq_forValueClass() { + val valueClass = ValueClass("Content") + mock().apply { + valueClass(valueClass) + verify(this).valueClass(eq(valueClass)) + } + } + + @Test + fun eq_withNestedValueClass() { + val nestedValueClass = NestedValueClass(ValueClass("Content")) + mock().apply { + nestedValueClass(nestedValueClass) + verify(this).nestedValueClass(eq(nestedValueClass)) + } + } + + @Test + fun eq_withClosedClass() { + val closedClassInstance = Closed() + mock().apply { + closed(closedClassInstance) + verify(this).closed(eq(closedClassInstance)) + } + } + + @Test + fun eq_withInt() { + mock().apply { + int(3) + verify(this).int(eq(3)) + } + } + /** * a VarargMatcher implementation for varargs of type [T] that will answer with type [R] if any of the var args * matched. Needs to keep state between matching invocations.