Support npc and object definitions in plugin tests

This commit is contained in:
Major
2018-08-21 00:47:23 +01:00
committed by Major-
parent 71158b3b5e
commit 86fba62ab9
10 changed files with 182 additions and 72 deletions
@@ -5,6 +5,8 @@ import io.mockk.slot
import io.mockk.spyk
import io.mockk.staticMockk
import org.apollo.cache.def.ItemDefinition
import org.apollo.cache.def.NpcDefinition
import org.apollo.cache.def.ObjectDefinition
import org.apollo.game.message.handler.MessageHandlerChainSet
import org.apollo.game.model.World
import org.apollo.game.model.entity.Npc
@@ -15,14 +17,25 @@ import org.apollo.game.plugin.PluginMetaData
import org.apollo.game.plugin.testing.fakes.FakePluginContextFactory
import org.apollo.game.plugin.testing.junit.api.ActionCapture
import org.apollo.game.plugin.testing.junit.api.annotations.ItemDefinitions
import org.apollo.game.plugin.testing.junit.api.annotations.NpcDefinitions
import org.apollo.game.plugin.testing.junit.api.annotations.ObjectDefinitions
import org.apollo.game.plugin.testing.junit.mocking.StubPrototype
import org.junit.jupiter.api.extension.*
import java.util.*
import org.junit.jupiter.api.extension.AfterAllCallback
import org.junit.jupiter.api.extension.AfterEachCallback
import org.junit.jupiter.api.extension.AfterTestExecutionCallback
import org.junit.jupiter.api.extension.BeforeAllCallback
import org.junit.jupiter.api.extension.BeforeEachCallback
import org.junit.jupiter.api.extension.ExtensionContext
import org.junit.jupiter.api.extension.ParameterContext
import org.junit.jupiter.api.extension.ParameterResolver
import java.util.ArrayList
import kotlin.reflect.KFunction
import kotlin.reflect.KMutableProperty
import kotlin.reflect.full.companionObject
import kotlin.reflect.full.createType
import kotlin.reflect.full.declaredMemberFunctions
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.jvmErasure
internal val supportedTestDoubleTypes = setOf(
@@ -67,6 +80,25 @@ class ApolloTestingExtension :
val stubHandlers = MessageHandlerChainSet()
val stubWorld = spyk(World())
context.testClass // This _must_ come before plugin environment initialisation
.map { it.kotlin.companionObject }
.ifPresent { companion ->
val companionInstance = companion.objectInstance!!
val testClassMethods = companion.declaredMemberFunctions
createTestDefinitions<ItemDefinition, ItemDefinitions>(
testClassMethods, companionInstance, ItemDefinition::getId, ItemDefinition::lookup
)
createTestDefinitions<NpcDefinition, NpcDefinitions>(
testClassMethods, companionInstance, NpcDefinition::getId, NpcDefinition::lookup
)
createTestDefinitions<ObjectDefinition, ObjectDefinitions>(
testClassMethods, companionInstance, ObjectDefinition::getId, ObjectDefinition::lookup
)
}
val pluginEnvironment = KotlinPluginEnvironment(stubWorld)
pluginEnvironment.setContext(FakePluginContextFactory.create(stubHandlers))
pluginEnvironment.load(ArrayList<PluginMetaData>())
@@ -77,22 +109,8 @@ class ApolloTestingExtension :
}
override fun beforeEach(context: ExtensionContext) {
val testClass = context.requiredTestClass.kotlin
val testClassInstance = context.requiredTestInstance
val testClassProps = testClass.declaredMemberProperties
val testClassMethods = context.testClass.map { it.kotlin.declaredMemberFunctions }.orElse(emptyList())
val testClassItemDefs = testClassMethods.asSequence()
.mapNotNull { it.findAnnotation<ItemDefinitions>()?.let { anno -> it to anno } }
.flatMap { (it.first.call(context.requiredTestInstance as Any) as Collection<ItemDefinition>).asSequence() }
.map { it.id to it }
.toMap()
if (testClassItemDefs.isNotEmpty()) {
val itemIdSlot = slot<Int>()
staticMockk<ItemDefinition>().mock()
every { ItemDefinition.lookup(capture(itemIdSlot)) } answers { testClassItemDefs[itemIdSlot.captured] }
}
val testClassProps = context.requiredTestClass.kotlin.declaredMemberProperties
val store = context.getStore(namespace)
val state = store.get(ApolloTestState::class) as ApolloTestState
@@ -101,10 +119,10 @@ class ApolloTestingExtension :
.mapNotNull { it as? KMutableProperty<*> }
.filter { supportedTestDoubleTypes.contains(it.returnType) }
propertyStubSites.forEach {
it.setter.call(
propertyStubSites.forEach { property ->
property.setter.call(
testClassInstance,
state.createStub(StubPrototype(it.returnType.jvmErasure, it.annotations))
state.createStub(StubPrototype(property.returnType.jvmErasure, property.annotations))
)
}
}
@@ -124,4 +142,39 @@ class ApolloTestingExtension :
return testState.createStub(StubPrototype(paramType, param.annotations.toList()))
}
/**
* Mocks the definition class of type [D] for any function with the attached annotation [A].
*
* @param testClassMethods All of the methods in the class being tested.
* @param idMapper The map function that returns an id given a definition [D].
* @param lookup The lookup function that returns an instance of [D] given a definition id.
*/
private inline fun <reified D : Any, reified A : Annotation> createTestDefinitions(
testClassMethods: Collection<KFunction<*>>,
companionObjectInstance: Any?,
crossinline idMapper: (D) -> Int,
crossinline lookup: (Int) -> D
) {
val testDefinitions = testClassMethods.asSequence()
.filter { method -> method.annotations.any { it is A } }
.flatMap { method ->
@Suppress("UNCHECKED_CAST")
method as? KFunction<Collection<D>> ?: throw RuntimeException("${method.name} is annotated with " +
"${A::class.simpleName} but does not return Collection<${D::class.simpleName}."
)
method.isAccessible = true // lets us call methods in private companion objects
method.call(companionObjectInstance).asSequence()
}
.associateBy(idMapper)
if (testDefinitions.isNotEmpty()) {
val idSlot = slot<Int>()
staticMockk<D>().mock()
every { lookup(capture(idSlot)) } answers { testDefinitions[idSlot.captured]!! }
}
}
}
@@ -1,5 +1,37 @@
package org.apollo.game.plugin.testing.junit.api.annotations
/**
* Specifies that the the ItemDefinitions returned by the annotated function should be inserted into the definition
* table.
*
* The annotated function **must**:
* - Be inside a **companion object** inside an apollo test class (a regular object will not work).
* - Return a `Collection<ItemDefinition>`.
*/
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class ItemDefinitions
annotation class ItemDefinitions
/**
* Specifies that the the NpcDefinitions returned by the annotated function should be inserted into the definition
* table.
*
* The annotated function **must**:
* - Be inside a **companion object** inside an apollo test class (a regular object will not work).
* - Return a `Collection<NpcDefinition>`.
*/
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class NpcDefinitions
/**
* Specifies that the the ObjectDefinitions returned by the annotated function should be inserted into the definition
* table.
*
* The annotated function **must**:
* - Be inside a **companion object** inside an apollo test class (a regular object will not work).
* - Return a `Collection<ObjectDefinition>`.
*/
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class ObjectDefinitions