From a48a030769b54b9fc9506e2ba2068bd058d3ff20 Mon Sep 17 00:00:00 2001 From: Jan Skrasek Date: Wed, 16 Jun 2021 10:47:30 +0200 Subject: [PATCH 1/4] pass nullability information to AnyValueGenerator --- dsl/common/src/main/kotlin/io/mockk/API.kt | 1 + .../impl/instantiation/AnyValueGenerator.kt | 2 +- .../io/mockk/impl/recording/WasNotCalled.kt | 3 +- .../impl/recording/states/RecordingState.kt | 4 +- .../kotlin/io/mockk/impl/stub/MockKStub.kt | 5 ++- .../instantiation/AnyValueGeneratorTest.kt | 44 +++++++++---------- .../kotlin/io/mockk/it/RelaxedMockingTest.kt | 9 +++- .../mockk/impl/instantiation/JsMockFactory.kt | 2 + .../instantiation/JvmAnyValueGenerator.kt | 4 +- .../impl/instantiation/JvmMockFactory.kt | 2 +- .../instantiation/JvmMockFactoryHelper.kt | 33 +++++++++----- .../io/mockk/jvm/JvmAnyValueGeneratorTest.kt | 44 +++++++++---------- 12 files changed, 90 insertions(+), 63 deletions(-) diff --git a/dsl/common/src/main/kotlin/io/mockk/API.kt b/dsl/common/src/main/kotlin/io/mockk/API.kt index 052f06977..ce055a907 100644 --- a/dsl/common/src/main/kotlin/io/mockk/API.kt +++ b/dsl/common/src/main/kotlin/io/mockk/API.kt @@ -3600,6 +3600,7 @@ data class Call( data class MethodDescription( val name: String, val returnType: KClass<*>, + val returnTypeNullable: Boolean, val returnsUnit: Boolean, val returnsNothing: Boolean, val isSuspend: Boolean, diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/instantiation/AnyValueGenerator.kt b/mockk/common/src/main/kotlin/io/mockk/impl/instantiation/AnyValueGenerator.kt index a540d146d..8f3d68127 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/instantiation/AnyValueGenerator.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/instantiation/AnyValueGenerator.kt @@ -3,7 +3,7 @@ package io.mockk.impl.instantiation import kotlin.reflect.KClass open class AnyValueGenerator { - open fun anyValue(cls: KClass<*>, orInstantiateVia: () -> Any?): Any? { + open fun anyValue(cls: KClass<*>, isNullable: Boolean, orInstantiateVia: () -> Any?): Any? { return when (cls) { Boolean::class -> false Byte::class -> 0.toByte() diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/recording/WasNotCalled.kt b/mockk/common/src/main/kotlin/io/mockk/impl/recording/WasNotCalled.kt index 846007485..e4aba85f6 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/recording/WasNotCalled.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/recording/WasNotCalled.kt @@ -6,6 +6,7 @@ object WasNotCalled { val method = MethodDescription( "wasNot Called", Unit::class, + false, true, false, false, @@ -15,4 +16,4 @@ object WasNotCalled { -1, false ) -} \ No newline at end of file +} diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/RecordingState.kt b/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/RecordingState.kt index 09189bebb..199dbfe1c 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/RecordingState.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/RecordingState.kt @@ -45,7 +45,7 @@ abstract class RecordingState(recorder: CommonCallRecorder) : CallRecordingState @Suppress("UNCHECKED_CAST") override fun matcher(matcher: Matcher<*>, cls: KClass): T { val signatureValue = recorder.signatureValueGenerator.signatureValue(cls) { - recorder.anyValueGenerator.anyValue(cls) { + recorder.anyValueGenerator.anyValue(cls, isNullable = false) { recorder.instantiator.instantiate(cls) } as T } @@ -67,7 +67,7 @@ abstract class RecordingState(recorder: CommonCallRecorder) : CallRecordingState if (invocation.method.isToString()) { recorder.stubRepo[invocation.self]?.toStr() ?: "" } else { - recorder.anyValueGenerator.anyValue(retType) { + recorder.anyValueGenerator.anyValue(retType, invocation.method.returnTypeNullable) { isTemporaryMock = true recorder.mockFactory.temporaryMock(retType) } diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/stub/MockKStub.kt b/mockk/common/src/main/kotlin/io/mockk/impl/stub/MockKStub.kt index bcc764ead..53d511f26 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/stub/MockKStub.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/stub/MockKStub.kt @@ -83,7 +83,10 @@ open class MockKStub( return stdObjectFunctions(invocation.self, invocation.method, invocation.args) { if (shouldRelax(invocation)) { if (invocation.method.returnsUnit) return Unit - return gatewayAccess.anyValueGenerator.anyValue(invocation.method.returnType) { + return gatewayAccess.anyValueGenerator.anyValue( + invocation.method.returnType, + invocation.method.returnTypeNullable + ) { childMockK(invocation.allEqMatcher(), invocation.method.returnType) } } else { diff --git a/mockk/common/src/test/kotlin/io/mockk/impl/instantiation/AnyValueGeneratorTest.kt b/mockk/common/src/test/kotlin/io/mockk/impl/instantiation/AnyValueGeneratorTest.kt index b45635dd0..aeee59a33 100644 --- a/mockk/common/src/test/kotlin/io/mockk/impl/instantiation/AnyValueGeneratorTest.kt +++ b/mockk/common/src/test/kotlin/io/mockk/impl/instantiation/AnyValueGeneratorTest.kt @@ -12,111 +12,111 @@ class AnyValueGeneratorTest { @Test fun givenByteClassWhenRequestedForAnyValueThen0IsReturned() { - assertEquals(0.toByte(), generator.anyValue(Byte::class, failOnPassThrough)) + assertEquals(0.toByte(), generator.anyValue(Byte::class, false, failOnPassThrough)) } @Test fun givenShortClassWhenRequestedForAnyValueThen0IsReturned() { - assertEquals(0.toShort(), generator.anyValue(Short::class, failOnPassThrough)) + assertEquals(0.toShort(), generator.anyValue(Short::class, false, failOnPassThrough)) } @Test fun givenCharClassWhenRequestedForAnyValueThen0IsReturned() { - assertEquals(0.toChar(), generator.anyValue(Char::class, failOnPassThrough)) + assertEquals(0.toChar(), generator.anyValue(Char::class, false, failOnPassThrough)) } @Test fun givenIntClassWhenRequestedForAnyValueThen0IsReturned() { - assertEquals(0, generator.anyValue(Int::class, failOnPassThrough)) + assertEquals(0, generator.anyValue(Int::class, false, failOnPassThrough)) } @Test fun givenLongClassWhenRequestedForAnyValueThen0IsReturned() { - assertEquals(0L, generator.anyValue(Long::class, failOnPassThrough)) + assertEquals(0L, generator.anyValue(Long::class, false, failOnPassThrough)) } @Test fun givenFloatClassWhenRequestedForAnyValueThen0IsReturned() { - assertEquals(0F, generator.anyValue(Float::class, failOnPassThrough)) + assertEquals(0F, generator.anyValue(Float::class, false, failOnPassThrough)) } @Test fun givenDoubleClassWhenRequestedForAnyValueThen0IsReturned() { - assertEquals(0.0, generator.anyValue(Double::class, failOnPassThrough)) + assertEquals(0.0, generator.anyValue(Double::class, false, failOnPassThrough)) } @Test fun givenStringClassWhenRequestedForAnyValueThenEmptyStringIsReturned() { - assertEquals("", generator.anyValue(String::class, failOnPassThrough)) + assertEquals("", generator.anyValue(String::class, false, failOnPassThrough)) } @Test fun givenBooleanArrayClassWhenRequestedForAnyValueThenEmptyBooleanArrayIsReturned() { - assertArrayEquals(BooleanArray(0), generator.anyValue(BooleanArray::class, failOnPassThrough) as BooleanArray) + assertArrayEquals(BooleanArray(0), generator.anyValue(BooleanArray::class, false, failOnPassThrough) as BooleanArray) } @Test fun givenByteArrayClassWhenRequestedForAnyValueThenEmptyByteArrayIsReturned() { - assertArrayEquals(ByteArray(0), generator.anyValue(ByteArray::class, failOnPassThrough) as ByteArray) + assertArrayEquals(ByteArray(0), generator.anyValue(ByteArray::class, false, failOnPassThrough) as ByteArray) } @Test fun givenCharArrayClassWhenRequestedForAnyValueThenEmptyCharArrayIsReturned() { - assertArrayEquals(CharArray(0), generator.anyValue(CharArray::class, failOnPassThrough) as CharArray) + assertArrayEquals(CharArray(0), generator.anyValue(CharArray::class, false, failOnPassThrough) as CharArray) } @Test fun givenShortArrayClassWhenRequestedForAnyValueThenEmptyShortArrayIsReturned() { - assertArrayEquals(ShortArray(0), generator.anyValue(ShortArray::class, failOnPassThrough) as ShortArray) + assertArrayEquals(ShortArray(0), generator.anyValue(ShortArray::class, false, failOnPassThrough) as ShortArray) } @Test fun givenIntArrayClassWhenRequestedForAnyValueThenEmptyIntArrayIsReturned() { - assertArrayEquals(IntArray(0), generator.anyValue(IntArray::class, failOnPassThrough) as IntArray) + assertArrayEquals(IntArray(0), generator.anyValue(IntArray::class, false, failOnPassThrough) as IntArray) } @Test fun givenLongArrayClassWhenRequestedForAnyValueThenEmptyLongArrayIsReturned() { - assertArrayEquals(LongArray(0), generator.anyValue(LongArray::class, failOnPassThrough) as LongArray) + assertArrayEquals(LongArray(0), generator.anyValue(LongArray::class, false, failOnPassThrough) as LongArray) } @Test fun givenFloatArrayClassWhenRequestedForAnyValueThenEmptyFloatArrayIsReturned() { - assertArrayEquals(FloatArray(0), generator.anyValue(FloatArray::class, failOnPassThrough) as FloatArray, 1e-6f) + assertArrayEquals(FloatArray(0), generator.anyValue(FloatArray::class, false, failOnPassThrough) as FloatArray, 1e-6f) } @Test fun givenDoubleArrayClassWhenRequestedForAnyValueThenEmptyDoubleArrayIsReturned() { - assertArrayEquals(DoubleArray(0), generator.anyValue(DoubleArray::class, failOnPassThrough) as DoubleArray, 1e-6) + assertArrayEquals(DoubleArray(0), generator.anyValue(DoubleArray::class, false, failOnPassThrough) as DoubleArray, 1e-6) } @Test fun givenListClassWhenRequestedForAnyValueThenEmptyListIsReturned() { - assertEquals(listOf(), generator.anyValue(List::class, failOnPassThrough) as List<*>) + assertEquals(listOf(), generator.anyValue(List::class, false, failOnPassThrough) as List<*>) } @Test fun givenMapClassWhenRequestedForAnyValueThenEmptyMapIsReturned() { - assertEquals(mapOf(), generator.anyValue(Map::class, failOnPassThrough) as Map<*, *>) + assertEquals(mapOf(), generator.anyValue(Map::class, false, failOnPassThrough) as Map<*, *>) } @Test fun givenSetClassWhenRequestedForAnyValueThenEmptySetIsReturned() { - assertEquals(setOf(), generator.anyValue(Set::class, failOnPassThrough) as Set<*>) + assertEquals(setOf(), generator.anyValue(Set::class, false, failOnPassThrough) as Set<*>) } @Test fun givenArrayListClassWhenRequestedForAnyValueThenEmptyArrayListIsReturned() { - assertEquals(arrayListOf(), generator.anyValue(ArrayList::class, failOnPassThrough) as ArrayList<*>) + assertEquals(arrayListOf(), generator.anyValue(ArrayList::class, false, failOnPassThrough) as ArrayList<*>) } @Test fun givenHashMapClassWhenRequestedForAnyValueThenEmptyHashMapIsReturned() { - assertEquals(hashMapOf(), generator.anyValue(HashMap::class, failOnPassThrough) as HashMap<*, *>) + assertEquals(hashMapOf(), generator.anyValue(HashMap::class, false, failOnPassThrough) as HashMap<*, *>) } @Test fun givenHashSetClassWhenRequestedForAnyValueThenEmptyHashSetIsReturned() { - assertEquals(hashSetOf(), generator.anyValue(HashSet::class, failOnPassThrough) as HashSet<*>) + assertEquals(hashSetOf(), generator.anyValue(HashSet::class, false, failOnPassThrough) as HashSet<*>) } } diff --git a/mockk/common/src/test/kotlin/io/mockk/it/RelaxedMockingTest.kt b/mockk/common/src/test/kotlin/io/mockk/it/RelaxedMockingTest.kt index caa969eb1..fe4daab9b 100644 --- a/mockk/common/src/test/kotlin/io/mockk/it/RelaxedMockingTest.kt +++ b/mockk/common/src/test/kotlin/io/mockk/it/RelaxedMockingTest.kt @@ -51,5 +51,12 @@ class RelaxedMockingTest { assertEquals(2, slot.captured) } + @Test + fun testRelaxedFunction() { + val block = mockk<() -> Unit>(relaxed = true) + block() + verify { block.invoke() } + } + private fun mockCls() = mockk(relaxUnitFun = true) -} \ No newline at end of file +} diff --git a/mockk/js/src/main/kotlin/io/mockk/impl/instantiation/JsMockFactory.kt b/mockk/js/src/main/kotlin/io/mockk/impl/instantiation/JsMockFactory.kt index e46fe563b..16e43f291 100644 --- a/mockk/js/src/main/kotlin/io/mockk/impl/instantiation/JsMockFactory.kt +++ b/mockk/js/src/main/kotlin/io/mockk/impl/instantiation/JsMockFactory.kt @@ -87,6 +87,7 @@ internal class StubProxyHandler( false, false, false, + false, cls, listOf(), -1, @@ -112,6 +113,7 @@ internal class StubProxyHandler( false, false, false, + false, cls, listOf(), -1, diff --git a/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmAnyValueGenerator.kt b/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmAnyValueGenerator.kt index 04253b6c9..e986a4639 100644 --- a/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmAnyValueGenerator.kt +++ b/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmAnyValueGenerator.kt @@ -6,7 +6,7 @@ class JvmAnyValueGenerator( private val voidInstance: Any ) : AnyValueGenerator() { - override fun anyValue(cls: KClass<*>, orInstantiateVia: () -> Any?): Any? { + override fun anyValue(cls: KClass<*>, isNullable: Boolean, orInstantiateVia: () -> Any?): Any? { return when (cls) { Void.TYPE.kotlin -> voidInstance Void::class -> voidInstance @@ -28,7 +28,7 @@ class JvmAnyValueGenerator( java.util.HashMap::class -> HashMap() java.util.HashSet::class -> HashSet() - else -> super.anyValue(cls) { + else -> super.anyValue(cls, isNullable) { if (cls.java.isArray) { java.lang.reflect.Array.newInstance(cls.java.componentType, 0) } else { diff --git a/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactory.kt b/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactory.kt index f20e76d7d..1cfdff7fb 100644 --- a/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactory.kt +++ b/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactory.kt @@ -51,7 +51,7 @@ class JvmMockFactory( "This can help if it's last call in the chain" } - gatewayAccess.anyValueGenerator.anyValue(cls) { + gatewayAccess.anyValueGenerator.anyValue(cls, isNullable = false) { instantiator.instantiate(cls) } as T } diff --git a/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactoryHelper.kt b/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactoryHelper.kt index 4d7cf66c1..71552c367 100644 --- a/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactoryHelper.kt +++ b/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactoryHelper.kt @@ -9,10 +9,7 @@ import java.lang.reflect.Method import java.lang.reflect.Modifier import java.util.concurrent.Callable import kotlin.coroutines.Continuation -import kotlin.reflect.KClass -import kotlin.reflect.KMutableProperty -import kotlin.reflect.KParameter -import kotlin.reflect.KType +import kotlin.reflect.* import kotlin.reflect.full.memberProperties import kotlin.reflect.jvm.javaField import kotlin.reflect.jvm.javaMethod @@ -35,15 +32,28 @@ object JvmMockFactoryHelper { } } + private fun findBackingField(clazz: KClass<*>, method: Method): KProperty1<*, *>? = runCatching { + /** + * `runCatching()` is used to avoid crashes when analyzing unsupported types as + * top-file extension function resulting into + * `Packages and file facades are not yet supported in Kotlin reflection.` + * + * Also, the functional types are unsupported, we skip them early. + */ + + if (Function::class.java.isAssignableFrom(clazz.java)) { + return null + } + + return clazz.memberProperties.firstOrNull { + it.getter.javaMethod == method || + (it is KMutableProperty<*> && it.setter.javaMethod == method) + } + }.getOrNull() private fun findBackingField(self: Any, method: Method): BackingFieldValueProvider { return { - val property = self::class.memberProperties.firstOrNull { - it.getter.javaMethod == method || - (it is KMutableProperty<*> && it.setter.javaMethod == method) - } - - + val property = findBackingField(self::class, method) property?.javaField?.let { field -> BackingFieldValue( property.name, @@ -136,6 +146,7 @@ object JvmMockFactoryHelper { val isFnCall = Function::class.java.isAssignableFrom(declaringClass) val kotlinReturnType = kotlinFunc?.returnType + ?: findBackingField(declaringClass.kotlin, this)?.returnType val returnType: KClass<*> = when (kotlinReturnType) { is KType -> kotlinReturnType.classifier as? KClass<*> ?: returnType.kotlin is KClass<*> -> kotlinReturnType @@ -147,10 +158,12 @@ object JvmMockFactoryHelper { } else { returnType } + val returnTypeNullable = kotlinReturnType?.isMarkedNullable ?: false val result = MethodDescription( name, androidCompatibleReturnType, + returnTypeNullable, returnTypeIsUnit, returnTypeIsNothing, isSuspend, diff --git a/mockk/jvm/src/test/kotlin/io/mockk/jvm/JvmAnyValueGeneratorTest.kt b/mockk/jvm/src/test/kotlin/io/mockk/jvm/JvmAnyValueGeneratorTest.kt index 6167a1cff..5f9a08600 100644 --- a/mockk/jvm/src/test/kotlin/io/mockk/jvm/JvmAnyValueGeneratorTest.kt +++ b/mockk/jvm/src/test/kotlin/io/mockk/jvm/JvmAnyValueGeneratorTest.kt @@ -14,111 +14,111 @@ class JvmAnyValueGeneratorTest { @Test fun givenByteClassWhenRequestedForAnyValueThen0IsReturned() { - assertEquals(0.toByte(), generator.anyValue(Byte::class, failOnPassThrough)) + assertEquals(0.toByte(), generator.anyValue(Byte::class, false, failOnPassThrough)) } @Test fun givenShortClassWhenRequestedForAnyValueThen0IsReturned() { - assertEquals(0.toShort(), generator.anyValue(Short::class, failOnPassThrough)) + assertEquals(0.toShort(), generator.anyValue(Short::class, false, failOnPassThrough)) } @Test fun givenCharClassWhenRequestedForAnyValueThen0IsReturned() { - assertEquals(0.toChar(), generator.anyValue(Char::class, failOnPassThrough)) + assertEquals(0.toChar(), generator.anyValue(Char::class, false, failOnPassThrough)) } @Test fun givenIntClassWhenRequestedForAnyValueThen0IsReturned() { - assertEquals(0, generator.anyValue(Int::class, failOnPassThrough)) + assertEquals(0, generator.anyValue(Int::class, false, failOnPassThrough)) } @Test fun givenLongClassWhenRequestedForAnyValueThen0IsReturned() { - assertEquals(0L, generator.anyValue(Long::class, failOnPassThrough)) + assertEquals(0L, generator.anyValue(Long::class, false, failOnPassThrough)) } @Test fun givenFloatClassWhenRequestedForAnyValueThen0IsReturned() { - assertEquals(0F, generator.anyValue(Float::class, failOnPassThrough)) + assertEquals(0F, generator.anyValue(Float::class, false, failOnPassThrough)) } @Test fun givenDoubleClassWhenRequestedForAnyValueThen0IsReturned() { - assertEquals(0.0, generator.anyValue(Double::class, failOnPassThrough)) + assertEquals(0.0, generator.anyValue(Double::class, false, failOnPassThrough)) } @Test fun givenStringClassWhenRequestedForAnyValueThenEmptyStringIsReturned() { - assertEquals("", generator.anyValue(String::class, failOnPassThrough)) + assertEquals("", generator.anyValue(String::class, false, failOnPassThrough)) } @Test fun givenBooleanArrayClassWhenRequestedForAnyValueThenEmptyBooleanArrayIsReturned() { - assertArrayEquals(BooleanArray(0), generator.anyValue(BooleanArray::class, failOnPassThrough) as BooleanArray) + assertArrayEquals(BooleanArray(0), generator.anyValue(BooleanArray::class, false, failOnPassThrough) as BooleanArray) } @Test fun givenByteArrayClassWhenRequestedForAnyValueThenEmptyByteArrayIsReturned() { - assertArrayEquals(ByteArray(0), generator.anyValue(ByteArray::class, failOnPassThrough) as ByteArray) + assertArrayEquals(ByteArray(0), generator.anyValue(ByteArray::class, false, failOnPassThrough) as ByteArray) } @Test fun givenCharArrayClassWhenRequestedForAnyValueThenEmptyCharArrayIsReturned() { - assertArrayEquals(CharArray(0), generator.anyValue(CharArray::class, failOnPassThrough) as CharArray) + assertArrayEquals(CharArray(0), generator.anyValue(CharArray::class, false, failOnPassThrough) as CharArray) } @Test fun givenShortArrayClassWhenRequestedForAnyValueThenEmptyShortArrayIsReturned() { - assertArrayEquals(ShortArray(0), generator.anyValue(ShortArray::class, failOnPassThrough) as ShortArray) + assertArrayEquals(ShortArray(0), generator.anyValue(ShortArray::class, false, failOnPassThrough) as ShortArray) } @Test fun givenIntArrayClassWhenRequestedForAnyValueThenEmptyIntArrayIsReturned() { - assertArrayEquals(IntArray(0), generator.anyValue(IntArray::class, failOnPassThrough) as IntArray) + assertArrayEquals(IntArray(0), generator.anyValue(IntArray::class, false, failOnPassThrough) as IntArray) } @Test fun givenLongArrayClassWhenRequestedForAnyValueThenEmptyLongArrayIsReturned() { - assertArrayEquals(LongArray(0), generator.anyValue(LongArray::class, failOnPassThrough) as LongArray) + assertArrayEquals(LongArray(0), generator.anyValue(LongArray::class, false, failOnPassThrough) as LongArray) } @Test fun givenFloatArrayClassWhenRequestedForAnyValueThenEmptyFloatArrayIsReturned() { - assertArrayEquals(FloatArray(0), generator.anyValue(FloatArray::class, failOnPassThrough) as FloatArray, 1e-6f) + assertArrayEquals(FloatArray(0), generator.anyValue(FloatArray::class, false, failOnPassThrough) as FloatArray, 1e-6f) } @Test fun givenDoubleArrayClassWhenRequestedForAnyValueThenEmptyDoubleArrayIsReturned() { - assertArrayEquals(DoubleArray(0), generator.anyValue(DoubleArray::class, failOnPassThrough) as DoubleArray, 1e-6) + assertArrayEquals(DoubleArray(0), generator.anyValue(DoubleArray::class, false, failOnPassThrough) as DoubleArray, 1e-6) } @Test fun givenListClassWhenRequestedForAnyValueThenEmptyListIsReturned() { - assertEquals(listOf(), generator.anyValue(List::class, failOnPassThrough) as List<*>) + assertEquals(listOf(), generator.anyValue(List::class, false, failOnPassThrough) as List<*>) } @Test fun givenMapClassWhenRequestedForAnyValueThenEmptyMapIsReturned() { - assertEquals(mapOf(), generator.anyValue(Map::class, failOnPassThrough) as Map<*, *>) + assertEquals(mapOf(), generator.anyValue(Map::class, false, failOnPassThrough) as Map<*, *>) } @Test fun givenSetClassWhenRequestedForAnyValueThenEmptySetIsReturned() { - assertEquals(setOf(), generator.anyValue(Set::class, failOnPassThrough) as Set<*>) + assertEquals(setOf(), generator.anyValue(Set::class, false, failOnPassThrough) as Set<*>) } @Test fun givenArrayListClassWhenRequestedForAnyValueThenEmptyArrayListIsReturned() { - assertEquals(arrayListOf(), generator.anyValue(ArrayList::class, failOnPassThrough) as ArrayList<*>) + assertEquals(arrayListOf(), generator.anyValue(ArrayList::class, false, failOnPassThrough) as ArrayList<*>) } @Test fun givenHashMapClassWhenRequestedForAnyValueThenEmptyHashMapIsReturned() { - assertEquals(hashMapOf(), generator.anyValue(HashMap::class, failOnPassThrough) as HashMap<*, *>) + assertEquals(hashMapOf(), generator.anyValue(HashMap::class, false, failOnPassThrough) as HashMap<*, *>) } @Test fun givenHashSetClassWhenRequestedForAnyValueThenEmptyHashSetIsReturned() { - assertEquals(hashSetOf(), generator.anyValue(HashSet::class, failOnPassThrough) as HashSet<*>) + assertEquals(hashSetOf(), generator.anyValue(HashSet::class, false, failOnPassThrough) as HashSet<*>) } } From 070ab2d2867bb6b02a13ad8c96e3456486b29bbf Mon Sep 17 00:00:00 2001 From: Jan Skrasek Date: Wed, 16 Jun 2021 10:50:51 +0200 Subject: [PATCH 2/4] allow custom AnyValueGenerator --- .../impl/recording/CommonCallRecorder.kt | 2 +- .../impl/recording/states/RecordingState.kt | 4 +- .../kotlin/io/mockk/impl/stub/MockKStub.kt | 2 +- .../io/mockk/impl/stub/StubGatewayAccess.kt | 2 +- .../kotlin/io/mockk/impl/JsMockKGateway.kt | 4 +- .../kotlin/io/mockk/impl/JvmMockKGateway.kt | 19 ++++++-- .../instantiation/JvmAnyValueGenerator.kt | 2 +- .../impl/instantiation/JvmMockFactory.kt | 2 +- .../io/mockk/it/NullableValueGeneratorTest.kt | 48 +++++++++++++++++++ 9 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 mockk/jvm/src/test/kotlin/io/mockk/it/NullableValueGeneratorTest.kt diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/recording/CommonCallRecorder.kt b/mockk/common/src/main/kotlin/io/mockk/impl/recording/CommonCallRecorder.kt index 2c8d686c2..a582fae82 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/recording/CommonCallRecorder.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/recording/CommonCallRecorder.kt @@ -17,7 +17,7 @@ class CommonCallRecorder( val instantiator: AbstractInstantiator, val signatureValueGenerator: SignatureValueGenerator, val mockFactory: MockFactory, - val anyValueGenerator: AnyValueGenerator, + val anyValueGenerator: () -> AnyValueGenerator, val safeToString: SafeToString, val factories: CallRecorderFactories, val initialState: (CommonCallRecorder) -> CallRecordingState, diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/RecordingState.kt b/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/RecordingState.kt index 199dbfe1c..5c594cb05 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/RecordingState.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/recording/states/RecordingState.kt @@ -45,7 +45,7 @@ abstract class RecordingState(recorder: CommonCallRecorder) : CallRecordingState @Suppress("UNCHECKED_CAST") override fun matcher(matcher: Matcher<*>, cls: KClass): T { val signatureValue = recorder.signatureValueGenerator.signatureValue(cls) { - recorder.anyValueGenerator.anyValue(cls, isNullable = false) { + recorder.anyValueGenerator().anyValue(cls, isNullable = false) { recorder.instantiator.instantiate(cls) } as T } @@ -67,7 +67,7 @@ abstract class RecordingState(recorder: CommonCallRecorder) : CallRecordingState if (invocation.method.isToString()) { recorder.stubRepo[invocation.self]?.toStr() ?: "" } else { - recorder.anyValueGenerator.anyValue(retType, invocation.method.returnTypeNullable) { + recorder.anyValueGenerator().anyValue(retType, invocation.method.returnTypeNullable) { isTemporaryMock = true recorder.mockFactory.temporaryMock(retType) } diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/stub/MockKStub.kt b/mockk/common/src/main/kotlin/io/mockk/impl/stub/MockKStub.kt index 53d511f26..e25022e1a 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/stub/MockKStub.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/stub/MockKStub.kt @@ -83,7 +83,7 @@ open class MockKStub( return stdObjectFunctions(invocation.self, invocation.method, invocation.args) { if (shouldRelax(invocation)) { if (invocation.method.returnsUnit) return Unit - return gatewayAccess.anyValueGenerator.anyValue( + return gatewayAccess.anyValueGenerator().anyValue( invocation.method.returnType, invocation.method.returnTypeNullable ) { diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/stub/StubGatewayAccess.kt b/mockk/common/src/main/kotlin/io/mockk/impl/stub/StubGatewayAccess.kt index 2454810bf..6cbd63e4c 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/stub/StubGatewayAccess.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/stub/StubGatewayAccess.kt @@ -7,7 +7,7 @@ import io.mockk.impl.log.SafeToString data class StubGatewayAccess( val callRecorder: () -> CallRecorder, - val anyValueGenerator: AnyValueGenerator, + val anyValueGenerator: () -> AnyValueGenerator, val stubRepository: StubRepository, val safeToString: SafeToString, val mockFactory: MockKGateway.MockFactory? = null diff --git a/mockk/js/src/main/kotlin/io/mockk/impl/JsMockKGateway.kt b/mockk/js/src/main/kotlin/io/mockk/impl/JsMockKGateway.kt index 47668f0fd..3f71dee1f 100644 --- a/mockk/js/src/main/kotlin/io/mockk/impl/JsMockKGateway.kt +++ b/mockk/js/src/main/kotlin/io/mockk/impl/JsMockKGateway.kt @@ -34,7 +34,7 @@ class JsMockKGateway : MockKGateway { override val mockFactory: MockFactory = JsMockFactory( stubRepo, instantiator, - StubGatewayAccess({ callRecorder }, anyValueGenerator, stubRepo, safeToString) + StubGatewayAccess({ callRecorder }, { anyValueGenerator }, stubRepo, safeToString) ) override val clearer = CommonClearer(stubRepo, safeToString) @@ -89,7 +89,7 @@ class JsMockKGateway : MockKGateway { instantiator, signatureValueGenerator, mockFactory, - anyValueGenerator, + { anyValueGenerator }, safeToString, callRecorderFactories, { recorder -> callRecorderFactories.answeringState(recorder) }, diff --git a/mockk/jvm/src/main/kotlin/io/mockk/impl/JvmMockKGateway.kt b/mockk/jvm/src/main/kotlin/io/mockk/impl/JvmMockKGateway.kt index 0270b2c68..7a388b341 100644 --- a/mockk/jvm/src/main/kotlin/io/mockk/impl/JvmMockKGateway.kt +++ b/mockk/jvm/src/main/kotlin/io/mockk/impl/JvmMockKGateway.kt @@ -55,11 +55,16 @@ class JvmMockKGateway : MockKGateway { instanceFactoryRegistryIntrnl ) - val anyValueGenerator = JvmAnyValueGenerator(instantiator.instantiate(Void::class)) + val anyValueGeneratorProvider: () -> AnyValueGenerator = { + if (anyValueGenerator == null) { + anyValueGenerator = anyValueGeneratorFactory.invoke(instantiator.instantiate(Void::class)) + } + anyValueGenerator!! + } val signatureValueGenerator = JvmSignatureValueGenerator(Random()) - val gatewayAccess = StubGatewayAccess({ callRecorder }, anyValueGenerator, stubRepo, safeToString) + val gatewayAccess = StubGatewayAccess({ callRecorder }, anyValueGeneratorProvider, stubRepo, safeToString) override val mockFactory: AbstractMockFactory = JvmMockFactory( agentFactory.proxyMaker, @@ -137,7 +142,7 @@ class JvmMockKGateway : MockKGateway { instantiator, signatureValueGenerator, mockFactory, - anyValueGenerator, + anyValueGeneratorProvider, safeToString, callRecorderFactories, { recorder -> callRecorderFactories.answeringState(recorder) }, @@ -170,6 +175,14 @@ class JvmMockKGateway : MockKGateway { } } + private var anyValueGenerator: AnyValueGenerator? = null + var anyValueGeneratorFactory: (voidInstance: Any) -> JvmAnyValueGenerator = + { voidInstance -> JvmAnyValueGenerator(voidInstance) } + set(value) { + anyValueGenerator = null + field = value + } + val defaultImplementation = JvmMockKGateway() val defaultImplementationBuilder = { defaultImplementation } } diff --git a/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmAnyValueGenerator.kt b/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmAnyValueGenerator.kt index e986a4639..968ed065e 100644 --- a/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmAnyValueGenerator.kt +++ b/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmAnyValueGenerator.kt @@ -2,7 +2,7 @@ package io.mockk.impl.instantiation import kotlin.reflect.KClass -class JvmAnyValueGenerator( +open class JvmAnyValueGenerator( private val voidInstance: Any ) : AnyValueGenerator() { diff --git a/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactory.kt b/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactory.kt index 1cfdff7fb..0c614fc7e 100644 --- a/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactory.kt +++ b/mockk/jvm/src/main/kotlin/io/mockk/impl/instantiation/JvmMockFactory.kt @@ -51,7 +51,7 @@ class JvmMockFactory( "This can help if it's last call in the chain" } - gatewayAccess.anyValueGenerator.anyValue(cls, isNullable = false) { + gatewayAccess.anyValueGenerator().anyValue(cls, isNullable = false) { instantiator.instantiate(cls) } as T } diff --git a/mockk/jvm/src/test/kotlin/io/mockk/it/NullableValueGeneratorTest.kt b/mockk/jvm/src/test/kotlin/io/mockk/it/NullableValueGeneratorTest.kt new file mode 100644 index 000000000..f5a50b815 --- /dev/null +++ b/mockk/jvm/src/test/kotlin/io/mockk/it/NullableValueGeneratorTest.kt @@ -0,0 +1,48 @@ +package io.mockk.it + +import io.mockk.impl.JvmMockKGateway +import io.mockk.impl.instantiation.JvmAnyValueGenerator +import io.mockk.mockk +import kotlinx.coroutines.runBlocking +import kotlin.reflect.KClass +import kotlin.test.Test +import kotlin.test.assertEquals + +@Suppress("UNUSED_PARAMETER") +class NullableValueGeneratorTest { + class NullableValueGenerator( + voidInstance: Any + ) : JvmAnyValueGenerator(voidInstance) { + override fun anyValue(cls: KClass<*>, isNullable: Boolean, orInstantiateVia: () -> Any?): Any? { + if (isNullable) return null + return super.anyValue(cls, isNullable, orInstantiateVia) + } + } + + @Test + fun testRelaxedMockReturnsNull() { + JvmMockKGateway.anyValueGeneratorFactory = { voidInstance -> + NullableValueGenerator(voidInstance) + } + + class Bar + + @Suppress("RedundantNullableReturnType", "RedundantSuspendModifier") + class Foo { + val property: Bar? = Bar() + val isEnabled: Boolean? = false + fun getSomething(): Bar? = Bar() + suspend fun getOtherThing(): Bar? = Bar() + } + + val mock = mockk(relaxed = true) + assertEquals(null, mock.property) + assertEquals(null, mock.isEnabled) + assertEquals(null, mock.getSomething()) + assertEquals(null, runBlocking { mock.getOtherThing() }) + + JvmMockKGateway.anyValueGeneratorFactory = { voidInstance -> + JvmAnyValueGenerator(voidInstance) + } + } +} From 4b4c63a4e545555a1d95e307a082155d7a123e02 Mon Sep 17 00:00:00 2001 From: Jan Skrasek Date: Wed, 23 Jun 2021 10:37:02 +0200 Subject: [PATCH 3/4] fix NoSuchMethodError --- .../src/main/kotlin/io/mockk/ValueClassSupport.kt | 13 +++++++++++-- .../src/main/kotlin/io/mockk/ValueClassSupport.kt | 13 +++++++++++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt b/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt index 77e432048..d53bf152c 100644 --- a/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt +++ b/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt @@ -30,10 +30,19 @@ fun T.boxedValue(): Any? { * @return class of boxed value, if this is value class, else just class of itself */ fun T.boxedClass(): KClass<*> { - if (!this::class.isValueClass()) return this::class + return this::class.boxedClass() +} + +/** + * Get the KClass of boxed value if this is a value class. + * + * @return class of boxed value, if this is value class, else just class of itself + */ +fun KClass<*>.boxedClass(): KClass<*> { + if (!this.isValueClass()) return this // get backing field - val backingField = this::class.valueField() + val backingField = this.valueField() // get boxed value return backingField.returnType.classifier as KClass<*> diff --git a/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt b/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt index 77e432048..d53bf152c 100644 --- a/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt +++ b/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt @@ -30,10 +30,19 @@ fun T.boxedValue(): Any? { * @return class of boxed value, if this is value class, else just class of itself */ fun T.boxedClass(): KClass<*> { - if (!this::class.isValueClass()) return this::class + return this::class.boxedClass() +} + +/** + * Get the KClass of boxed value if this is a value class. + * + * @return class of boxed value, if this is value class, else just class of itself + */ +fun KClass<*>.boxedClass(): KClass<*> { + if (!this.isValueClass()) return this // get backing field - val backingField = this::class.valueField() + val backingField = this.valueField() // get boxed value return backingField.returnType.classifier as KClass<*> From caea7cce8ac300991b8cf182e5d3524a4bc27ffd Mon Sep 17 00:00:00 2001 From: Jan Skrasek Date: Thu, 24 Jun 2021 10:59:41 +0200 Subject: [PATCH 4/4] protect isValueClass against KotlinReflectionInternalError exception --- agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt | 2 +- agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt | 2 +- mockk/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt b/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt index d53bf152c..1781141b5 100644 --- a/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt +++ b/agent/android/src/main/kotlin/io/mockk/ValueClassSupport.kt @@ -69,7 +69,7 @@ private fun KClass.valueField(): KProperty1 { private fun KClass.isValueClass() = try { this.isValue -} catch (_: UnsupportedOperationException) { +} catch (_: Throwable) { false } diff --git a/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt b/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt index d53bf152c..1781141b5 100644 --- a/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt +++ b/agent/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt @@ -69,7 +69,7 @@ private fun KClass.valueField(): KProperty1 { private fun KClass.isValueClass() = try { this.isValue -} catch (_: UnsupportedOperationException) { +} catch (_: Throwable) { false } diff --git a/mockk/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt b/mockk/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt index d53bf152c..1781141b5 100644 --- a/mockk/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt +++ b/mockk/jvm/src/main/kotlin/io/mockk/ValueClassSupport.kt @@ -69,7 +69,7 @@ private fun KClass.valueField(): KProperty1 { private fun KClass.isValueClass() = try { this.isValue -} catch (_: UnsupportedOperationException) { +} catch (_: Throwable) { false }