jvmMain.io.mockk.impl.annotations.JvmMockInitializer.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mockk-jvm Show documentation
Show all versions of mockk-jvm Show documentation
Mocking library for Kotlin
@file:Suppress("UNCHECKED_CAST")
package io.mockk.impl.annotations
import io.mockk.MockKException
import io.mockk.MockKGateway
import io.mockk.impl.annotations.InjectionHelpers.getAnyIfLateNull
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.isAccessible
class JvmMockInitializer(val gateway: MockKGateway) : MockKGateway.MockInitializer {
override fun initAnnotatedMocks(
targets: List,
overrideRecordPrivateCalls: Boolean,
relaxUnitFun: Boolean,
relaxed: Boolean
) {
for (target in targets) {
initMock(
target,
overrideRecordPrivateCalls,
relaxUnitFun,
relaxed
)
}
}
fun initMock(
target: Any,
overrideRecordPrivateCalls: Boolean,
relaxUnitFun: Boolean,
relaxed: Boolean
) {
val cls = target::class
for (property in cls.memberProperties) {
assignMockK(
property as KProperty1,
target,
relaxUnitFun,
relaxed
)
assignRelaxedMockK(property, target)
if (!isAnnotatedWith(property)) {
assignSpyK(
property,
target,
overrideRecordPrivateCalls
)
}
}
for (property in cls.memberProperties) {
property as KProperty1
property.annotated(target) { annotation ->
val mockInjector = MockInjector(
target,
annotation.lookupType,
annotation.injectImmutable,
annotation.overrideValues
)
val instance = doInjection(property, target, mockInjector)
convertSpyKAnnotatedToSpy(property, instance, overrideRecordPrivateCalls)
}
property.annotated(target) { annotation ->
val mockInjector = MockInjector(
target,
annotation.lookupType,
annotation.injectImmutable,
true
)
doInjection(property, target, mockInjector)
}
}
}
private fun doInjection(
property: KProperty1,
target: Any,
mockInjector: MockInjector
): Any {
return if (property is KMutableProperty1) {
val instance = (property as KMutableProperty1).getAnyIfLateNull(target)
?: mockInjector.constructorInjection(property.returnType.classifier as KClass<*>)
mockInjector.propertiesInjection(instance)
instance
} else {
val instance = mockInjector.constructorInjection(property.returnType.classifier as KClass<*>)
mockInjector.propertiesInjection(instance)
instance
}
}
private fun convertSpyKAnnotatedToSpy(property: KProperty1, instance: Any, overrideRecordPrivateCalls: Boolean): Any {
val spyAnnotation = property.findAnnotation() ?: return instance
return createSpyK(property, spyAnnotation, instance, overrideRecordPrivateCalls)
}
private fun assignSpyK(
property: KProperty1,
target: Any,
overrideRecordPrivateCalls: Boolean
) {
property.annotated(target) { annotation ->
val obj = property.get(target)
createSpyK(property, annotation, obj, overrideRecordPrivateCalls)
}
}
private fun createSpyK(
property: KProperty1,
spyAnnotation: SpyK,
instance: Any,
overrideRecordPrivateCalls: Boolean
): Any {
return gateway.mockFactory.spyk(
null,
instance,
overrideName(spyAnnotation.name, property.name),
moreInterfaces(property),
spyAnnotation.recordPrivateCalls
|| overrideRecordPrivateCalls
)
}
private fun assignRelaxedMockK(property: KProperty1, target: Any) {
property.annotated(target) { annotation ->
val type = property.returnType.classifier as? KClass<*>
?: return@annotated null
gateway.mockFactory.mockk(
type,
overrideName(annotation.name, property.name),
true,
moreInterfaces(property),
relaxUnitFun = false
)
}
}
private fun assignMockK(
property: KProperty1,
target: Any,
relaxUnitFun: Boolean,
relaxed: Boolean
) {
property.annotated(target) { annotation ->
val type = property.returnType.classifier as? KClass<*>
?: return@annotated null
gateway.mockFactory.mockk(
type,
overrideName(annotation.name, property.name),
annotation.relaxed ||
relaxed,
moreInterfaces(property),
relaxUnitFun =
annotation.relaxUnitFun ||
relaxUnitFun
)
}
}
private fun overrideName(annotationName: String, propertyName: String): String =
annotationName.ifBlank { propertyName }
private inline fun KProperty1.annotated(
target: Any,
block: (T) -> Any?
) {
val annotation = findAnnotation()
?: return
tryMakeAccessible(this)
if (isAlreadyInitialized(this, target)) return
val ret = block(annotation)
?: return
if (this !is KMutableProperty1) {
throw MockKException("Annotation $annotation present on $name read-only property, make it read-write please('lateinit var' or 'var')")
}
set(target, ret)
}
private fun isAlreadyInitialized(property: KProperty1, target: Any): Boolean {
try {
val value = property.get(target)
?: return false
return gateway.mockFactory.isMock(value)
} catch (ex: Exception) {
return false
}
}
private fun tryMakeAccessible(property: KProperty<*>) {
try {
property.isAccessible = true
} catch (ex: Exception) {
// skip
}
}
private inline fun isAnnotatedWith(property: KProperty<*>) : Boolean {
val annotation = property.findAnnotation()
return null != annotation
}
companion object {
private fun moreInterfaces(property: KProperty1) =
property.annotations
.filterIsInstance()
.map { it.type }
.toTypedArray()
}
}