mirror of
https://github.com/2006-Scape/apollo.git
synced 2026-07-04 00:38:11 +00:00
Update plugin test framework to junit5
Updates the testing infrastructure to use the latest relesae of junit and leverages the new extension mechanism to create an easy to use testing framework. Also adds additional test coverage for several plugins.
This commit is contained in:
@@ -5,9 +5,15 @@ dependencies {
|
||||
api project(':game')
|
||||
api project(':net')
|
||||
|
||||
api group: 'junit', name: 'junit', version: junitVersion
|
||||
api group: 'org.powermock', name: 'powermock-api-mockito', version: powermockVersion
|
||||
// JUnit Jupiter API and TestEngine implementation
|
||||
api("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}")
|
||||
api("org.junit.jupiter:junit-jupiter-params:${junitJupiterVersion}")
|
||||
implementation("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")
|
||||
implementation("org.junit.platform:junit-platform-launcher:${junitPlatformVersion}")
|
||||
|
||||
api group: 'io.mockk', name: 'mockk', version: mockkVersion
|
||||
api group: 'org.assertj', name: 'assertj-core', version: assertjVersion
|
||||
api group: 'com.willowtreeapps.assertk', name: 'assertk', version: assertkVersion
|
||||
|
||||
implementation group: 'org.powermock', name: 'powermock-module-junit4', version: powermockVersion
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
package org.apollo.game.plugin.testing
|
||||
|
||||
import org.apollo.game.message.handler.MessageHandlerChainSet
|
||||
import org.apollo.game.model.World
|
||||
import org.apollo.game.model.entity.Player
|
||||
import org.apollo.game.plugin.*
|
||||
import org.apollo.game.plugin.testing.fakes.FakePluginContextFactory
|
||||
import org.junit.Before
|
||||
import org.junit.runner.RunWith
|
||||
import org.powermock.api.mockito.PowerMockito
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest
|
||||
import org.powermock.modules.junit4.PowerMockRunner
|
||||
import java.util.*
|
||||
|
||||
@RunWith(PowerMockRunner::class)
|
||||
@PrepareForTest(World::class, PluginContext::class, Player::class)
|
||||
abstract class KotlinPluginTest: KotlinPluginTestHelpers() {
|
||||
override lateinit var world: World
|
||||
override lateinit var player: Player
|
||||
override lateinit var messageHandlers: MessageHandlerChainSet
|
||||
|
||||
@Before
|
||||
open fun setup() {
|
||||
messageHandlers = MessageHandlerChainSet()
|
||||
world = PowerMockito.spy(World())
|
||||
|
||||
val pluginEnvironment = KotlinPluginEnvironment(world)
|
||||
pluginEnvironment.setContext(FakePluginContextFactory.create(messageHandlers))
|
||||
pluginEnvironment.load(ArrayList<PluginMetaData>())
|
||||
|
||||
player = world.spawnPlayer("testPlayer")
|
||||
}
|
||||
|
||||
}
|
||||
-121
@@ -1,121 +0,0 @@
|
||||
package org.apollo.game.plugin.testing
|
||||
|
||||
import org.apollo.cache.def.ItemDefinition
|
||||
import org.apollo.cache.def.NpcDefinition
|
||||
import org.apollo.game.action.Action
|
||||
import org.apollo.game.message.handler.MessageHandlerChainSet
|
||||
import org.apollo.game.message.impl.*
|
||||
import org.apollo.game.model.*
|
||||
import org.apollo.game.model.entity.*
|
||||
import org.apollo.game.model.entity.obj.GameObject
|
||||
import org.apollo.game.model.entity.obj.StaticGameObject
|
||||
import org.apollo.net.message.Message
|
||||
import org.apollo.util.security.PlayerCredentials
|
||||
import org.junit.Assert
|
||||
import org.mockito.*
|
||||
import org.powermock.api.mockito.PowerMockito
|
||||
|
||||
/**
|
||||
* A base class containing a set of helper methods to be used within plugin tests.
|
||||
*/
|
||||
abstract class KotlinPluginTestHelpers {
|
||||
abstract var world: World
|
||||
abstract var player: Player
|
||||
abstract var messageHandlers: MessageHandlerChainSet
|
||||
|
||||
/**
|
||||
* Waits for an [Action] to complete within a specified number of pulses, and with an optional predicate
|
||||
* to test the [Action] against.
|
||||
*/
|
||||
fun Player.waitForActionCompletion(predicate: (Action<Player>) -> Boolean = { _ -> true }, timeout: Int = 15) {
|
||||
val actionCaptor: ArgumentCaptor<Action<*>> = ArgumentCaptor.forClass(Action::class.java)
|
||||
Mockito.verify(this).startAction(actionCaptor.capture())
|
||||
|
||||
val action: Action<Player> = actionCaptor.value as Action<Player>
|
||||
Assert.assertTrue("Found wrong action type", predicate.invoke(action))
|
||||
|
||||
var pulses = 0
|
||||
|
||||
do {
|
||||
action.pulse()
|
||||
|
||||
/**
|
||||
* Introducing an artificial delay is necessary to prevent the timeout being exceeded before
|
||||
* an asynchronous [Action] really starts. When a job is submitted to a new coroutine context
|
||||
* there may be a delay before it is actually executed.
|
||||
*
|
||||
* This delay is typically sub-millisecond and is only incurred with startup. Since game actions
|
||||
* have larger delays of their own this isn't a problem in practice.
|
||||
*/
|
||||
Thread.sleep(50L)
|
||||
} while (action.isRunning && pulses++ < timeout)
|
||||
|
||||
Assert.assertFalse("Exceeded timeout waiting for action completion", pulses > timeout)
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns a new NPC with the minimum set of dependencies required to function correctly in the world.
|
||||
*/
|
||||
fun World.spawnNpc(id: Int, position: Position): Npc {
|
||||
val definition = NpcDefinition(id)
|
||||
val npc = Npc(this, position, definition, arrayOfNulls(4))
|
||||
val region = regionRepository.fromPosition(position)
|
||||
val npcs = npcRepository
|
||||
|
||||
npcs.add(npc)
|
||||
region.addEntity(npc)
|
||||
|
||||
return npc
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn a new player stub in the world, with a dummy game session.
|
||||
*/
|
||||
fun World.spawnPlayer(username: String, position: Position = Position(3200, 3200, 0)): Player {
|
||||
val credentials = PlayerCredentials(username, "test", 1, 1, "0.0.0.0")
|
||||
val region = regionRepository.fromPosition(position)
|
||||
|
||||
val player = PowerMockito.spy(Player(this, credentials, position))
|
||||
register(player)
|
||||
region.addEntity(player)
|
||||
|
||||
PowerMockito.doNothing().`when`(player).send(Matchers.any())
|
||||
|
||||
return player
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn a new static game object into the world with the given id and position.
|
||||
*/
|
||||
fun World.spawnObject(id: Int, position: Position): GameObject {
|
||||
val obj = StaticGameObject(this, id, position, 0, 0)
|
||||
|
||||
spawn(obj)
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
/**
|
||||
* Fake a client [Message] originating from a player and send it to the relevant
|
||||
* message handlers.
|
||||
*/
|
||||
fun Player.notify(message: Message) {
|
||||
messageHandlers.notify(this, message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the player within interaction distance to the given [Entity] and fake an action
|
||||
* message.
|
||||
*/
|
||||
fun Player.interactWith(entity: Entity, option: Int = 1) {
|
||||
position = entity.position.step(1, Direction.NORTH)
|
||||
|
||||
when (entity) {
|
||||
is GameObject -> notify(ObjectActionMessage(option, entity.id, entity.position))
|
||||
is Npc -> notify(NpcActionMessage(option, entity.index))
|
||||
is Player -> notify(PlayerActionMessage(option, entity.index))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package org.apollo.game.plugin.testing.assertions
|
||||
|
||||
import io.mockk.MockKVerificationScope
|
||||
import io.mockk.verify
|
||||
import org.apollo.game.plugin.testing.junit.api.ActionCaptureCallbackRegistration
|
||||
|
||||
|
||||
/**
|
||||
* Verify some expectations on a [mock] after a delayed event (specified by [DelayMode]).
|
||||
*/
|
||||
fun verifyAfter(registration: ActionCaptureCallbackRegistration, description: String? = null, verifier: MockKVerificationScope.() -> Unit) {
|
||||
after(registration, description) { verify(verifyBlock = verifier) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a [callback] after a given delay, specified by [DelayMode].
|
||||
*/
|
||||
fun after(registration: ActionCaptureCallbackRegistration, description: String? = null, callback: () -> Unit) {
|
||||
registration.function = callback
|
||||
registration.description = description
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package org.apollo.game.plugin.testing.assertions
|
||||
|
||||
import io.mockk.MockKMatcherScope
|
||||
|
||||
inline fun MockKMatcherScope.contains(search: String) = match<String> { it.contains(search) }
|
||||
inline fun MockKMatcherScope.startsWith(search: String) = match<String> { it.startsWith(search) }
|
||||
inline fun MockKMatcherScope.endsWith(search: String) = match<String> { it.endsWith(search) }
|
||||
+12
-8
@@ -1,21 +1,25 @@
|
||||
package org.apollo.game.plugin.testing.fakes
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.slot
|
||||
import org.apollo.game.message.handler.MessageHandler
|
||||
import org.apollo.game.message.handler.MessageHandlerChainSet
|
||||
import org.apollo.game.plugin.PluginContext
|
||||
import org.apollo.net.message.Message
|
||||
import org.mockito.invocation.InvocationOnMock
|
||||
import org.mockito.stubbing.Answer
|
||||
import org.powermock.api.mockito.PowerMockito
|
||||
|
||||
object FakePluginContextFactory {
|
||||
fun create(messageHandlers: MessageHandlerChainSet): PluginContext {
|
||||
val answer = Answer<Any?> { invocation: InvocationOnMock ->
|
||||
messageHandlers.putHandler(
|
||||
invocation.arguments[0] as Class<Message>,
|
||||
invocation.arguments[1] as MessageHandler<*>)
|
||||
val ctx = mockk<PluginContext>()
|
||||
val typeCapture = slot<Class<Message>>()
|
||||
val handlerCapture = slot<MessageHandler<Message>>()
|
||||
|
||||
every {
|
||||
ctx.addMessageHandler(capture(typeCapture), capture(handlerCapture))
|
||||
} answers {
|
||||
messageHandlers.putHandler(typeCapture.captured, handlerCapture.captured)
|
||||
}
|
||||
|
||||
return PowerMockito.mock(PluginContext::class.java, answer)
|
||||
return ctx
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package org.apollo.game.plugin.testing.junit
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.slot
|
||||
import io.mockk.spyk
|
||||
import io.mockk.staticMockk
|
||||
import org.apollo.cache.def.ItemDefinition
|
||||
import org.apollo.game.message.handler.MessageHandlerChainSet
|
||||
import org.apollo.game.model.World
|
||||
import org.apollo.game.model.entity.Npc
|
||||
import org.apollo.game.model.entity.Player
|
||||
import org.apollo.game.model.entity.obj.GameObject
|
||||
import org.apollo.game.plugin.KotlinPluginEnvironment
|
||||
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.mocking.StubPrototype
|
||||
import org.junit.jupiter.api.extension.*
|
||||
import java.util.*
|
||||
import kotlin.reflect.KMutableProperty
|
||||
import kotlin.reflect.full.createType
|
||||
import kotlin.reflect.full.declaredMemberFunctions
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.jvm.jvmErasure
|
||||
|
||||
internal val supportedTestDoubleTypes = setOf(
|
||||
Player::class.createType(),
|
||||
Npc::class.createType(),
|
||||
GameObject::class.createType(),
|
||||
World::class.createType(),
|
||||
ActionCapture::class.createType()
|
||||
)
|
||||
|
||||
class ApolloTestingExtension :
|
||||
AfterTestExecutionCallback,
|
||||
BeforeAllCallback,
|
||||
AfterAllCallback,
|
||||
BeforeEachCallback,
|
||||
AfterEachCallback,
|
||||
ParameterResolver {
|
||||
|
||||
private val namespace = ExtensionContext.Namespace.create("apollo")
|
||||
|
||||
private fun cleanup(context: ExtensionContext) {
|
||||
val store = context.getStore(namespace)
|
||||
val state = store.get(ApolloTestState::class) as ApolloTestState
|
||||
|
||||
try {
|
||||
state.actionCapture?.runAction()
|
||||
} finally {
|
||||
state.reset()
|
||||
}
|
||||
}
|
||||
|
||||
override fun afterAll(context: ExtensionContext) {
|
||||
val store = context.getStore(namespace)
|
||||
store.remove(ApolloTestState::class)
|
||||
}
|
||||
|
||||
override fun afterEach(context: ExtensionContext) = cleanup(context)
|
||||
|
||||
override fun afterTestExecution(context: ExtensionContext) = cleanup(context)
|
||||
|
||||
override fun beforeAll(context: ExtensionContext) {
|
||||
val stubHandlers = MessageHandlerChainSet()
|
||||
val stubWorld = spyk(World())
|
||||
|
||||
val pluginEnvironment = KotlinPluginEnvironment(stubWorld)
|
||||
pluginEnvironment.setContext(FakePluginContextFactory.create(stubHandlers))
|
||||
pluginEnvironment.load(ArrayList<PluginMetaData>())
|
||||
|
||||
val state = ApolloTestState(stubHandlers, stubWorld)
|
||||
val store = context.getStore(namespace)
|
||||
store.put(ApolloTestState::class, state)
|
||||
}
|
||||
|
||||
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 store = context.getStore(namespace)
|
||||
val state = store.get(ApolloTestState::class) as ApolloTestState
|
||||
|
||||
val propertyStubSites = testClassProps.asSequence()
|
||||
.mapNotNull { it as? KMutableProperty<*> }
|
||||
.filter { supportedTestDoubleTypes.contains(it.returnType) }
|
||||
|
||||
propertyStubSites.forEach {
|
||||
it.setter.call(
|
||||
testClassInstance,
|
||||
state.createStub(StubPrototype(it.returnType.jvmErasure, it.annotations))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean {
|
||||
val param = parameterContext.parameter
|
||||
val paramType = param.type.kotlin
|
||||
|
||||
return supportedTestDoubleTypes.contains(paramType.createType())
|
||||
}
|
||||
|
||||
override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any {
|
||||
val param = parameterContext.parameter
|
||||
val paramType = param.type.kotlin
|
||||
val testStore = extensionContext.getStore(namespace)
|
||||
val testState = testStore.get(ApolloTestState::class) as ApolloTestState
|
||||
|
||||
return testState.createStub(StubPrototype(paramType, param.annotations.toList()))
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package org.apollo.game.plugin.testing.junit
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.slot
|
||||
import io.mockk.spyk
|
||||
import org.apollo.game.action.Action
|
||||
import org.apollo.game.message.handler.MessageHandlerChainSet
|
||||
import org.apollo.game.model.World
|
||||
import org.apollo.game.model.entity.Player
|
||||
import org.apollo.game.plugin.testing.junit.api.ActionCapture
|
||||
import org.apollo.game.plugin.testing.junit.mocking.StubPrototype
|
||||
import org.apollo.game.plugin.testing.junit.stubs.PlayerStubInfo
|
||||
import org.apollo.net.message.Message
|
||||
import org.apollo.util.security.PlayerCredentials
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
data class ApolloTestState(val handlers: MessageHandlerChainSet, val world: World) {
|
||||
val players = mutableListOf<Player>()
|
||||
var actionCapture: ActionCapture? = null
|
||||
|
||||
fun createActionCapture(type: KClass<out Action<*>>): ActionCapture {
|
||||
if (actionCapture != null) {
|
||||
throw IllegalStateException("Cannot specify more than one ActionCapture")
|
||||
}
|
||||
|
||||
actionCapture = ActionCapture(type)
|
||||
return actionCapture!!
|
||||
}
|
||||
|
||||
fun <T : Any> createStub(proto: StubPrototype<T>): T {
|
||||
val annotations = proto.annotations
|
||||
|
||||
return when (proto.type) {
|
||||
Player::class -> createPlayer(PlayerStubInfo.create(annotations)) as T
|
||||
World::class -> world as T
|
||||
ActionCapture::class -> createActionCapture(Action::class) as T
|
||||
else -> throw IllegalArgumentException("Can't stub ${proto.type.qualifiedName}")
|
||||
}
|
||||
}
|
||||
|
||||
fun createPlayer(info: PlayerStubInfo): Player {
|
||||
val credentials = PlayerCredentials(info.name, "test", 1, 1, "0.0.0.0")
|
||||
val region = world.regionRepository.fromPosition(info.position)
|
||||
|
||||
val player = spyk(Player(world, credentials, info.position))
|
||||
|
||||
world.register(player)
|
||||
region.addEntity(player)
|
||||
players.add(player)
|
||||
|
||||
val actionSlot = slot<Action<*>>()
|
||||
val messageSlot = slot<Message>()
|
||||
|
||||
every { player.send(capture(messageSlot)) } answers { handlers.notify(player, messageSlot.captured) }
|
||||
every { player.startAction(capture(actionSlot)) } answers {
|
||||
actionCapture?.capture(actionSlot.captured)
|
||||
true
|
||||
}
|
||||
|
||||
return player
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
actionCapture = null
|
||||
players.forEach {
|
||||
it.stopAction()
|
||||
world.unregister(it)
|
||||
}
|
||||
|
||||
players.clear()
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package org.apollo.game.plugin.testing.junit.api
|
||||
|
||||
import org.apollo.game.action.Action
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.isSuperclassOf
|
||||
|
||||
class ActionCapture(val type: KClass<out Action<*>>) {
|
||||
private var action: Action<*>? = null
|
||||
private val callbacks = mutableListOf<ActionCaptureCallback>()
|
||||
private var lastTicks: Int = 0
|
||||
|
||||
fun capture(captured: Action<*>) {
|
||||
assertTrue(type.isSuperclassOf(captured::class)) {
|
||||
"${captured::class.simpleName} is not an instance of ${type.simpleName}"
|
||||
}
|
||||
|
||||
this.action = captured
|
||||
}
|
||||
|
||||
private fun callback(delay: ActionCaptureDelay): ActionCaptureCallbackRegistration {
|
||||
val registration = ActionCaptureCallbackRegistration()
|
||||
val callback = ActionCaptureCallback(delay, registration)
|
||||
|
||||
callbacks.add(callback)
|
||||
return registration
|
||||
}
|
||||
|
||||
fun runAction(timeout: Int = 50) {
|
||||
action?.let {
|
||||
var pulses = 0
|
||||
|
||||
do {
|
||||
it.pulse()
|
||||
pulses++
|
||||
|
||||
val tickCallbacks = callbacks.filter { it.delay == ActionCaptureDelay.Ticks(pulses) }
|
||||
tickCallbacks.forEach { it.invoke() }
|
||||
|
||||
callbacks.removeAll(tickCallbacks)
|
||||
} while (it.isRunning && pulses < timeout)
|
||||
|
||||
val completionCallbacks = callbacks.filter { it.delay == ActionCaptureDelay.Completed }
|
||||
completionCallbacks.forEach { it.invoke() }
|
||||
|
||||
callbacks.removeAll(completionCallbacks)
|
||||
}
|
||||
|
||||
assertEquals(0, callbacks.size, {
|
||||
"untriggered callbacks:\n" + callbacks
|
||||
.map {
|
||||
val delayDescription = when (it.delay) {
|
||||
is ActionCaptureDelay.Ticks -> "${it.delay.count} ticks"
|
||||
is ActionCaptureDelay.Completed -> "action completion"
|
||||
}
|
||||
|
||||
"$delayDescription (${it.callbackRegistration.description ?: ""})"
|
||||
}
|
||||
.joinToString("\n")
|
||||
.prependIndent(" ")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a callback registration that triggers after exactly [count] ticks.
|
||||
*/
|
||||
fun exactTicks(count: Int) = callback(ActionCaptureDelay.Ticks(count))
|
||||
|
||||
/**
|
||||
* Create a callback registration that triggers after [count] ticks. This method is cumulative,
|
||||
* and will take into account previous calls to [ticks] when creating new callbacks.
|
||||
*
|
||||
* To run a callback after an exact number of ticks use [exactTicks].
|
||||
*/
|
||||
fun ticks(count: Int): ActionCaptureCallbackRegistration {
|
||||
lastTicks += count
|
||||
|
||||
return exactTicks(lastTicks)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a callback registration that triggers when an [Action] completes.
|
||||
*/
|
||||
fun complete() = callback(ActionCaptureDelay.Completed)
|
||||
|
||||
/**
|
||||
* Check if this capture has a pending [Action] to run.
|
||||
*/
|
||||
fun isPending(): Boolean {
|
||||
return action?.isRunning ?: false
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package org.apollo.game.plugin.testing.junit.api
|
||||
|
||||
data class ActionCaptureCallback(val delay: ActionCaptureDelay, val callbackRegistration: ActionCaptureCallbackRegistration) {
|
||||
fun invoke() {
|
||||
callbackRegistration.function?.invoke()
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package org.apollo.game.plugin.testing.junit.api
|
||||
|
||||
typealias Function = () -> Unit
|
||||
|
||||
class ActionCaptureCallbackRegistration(var function: Function? = null, var description: String? = null)
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package org.apollo.game.plugin.testing.junit.api
|
||||
|
||||
sealed class ActionCaptureDelay {
|
||||
data class Ticks(val count: Int) : ActionCaptureDelay()
|
||||
object Completed : ActionCaptureDelay()
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package org.apollo.game.plugin.testing.junit.api.annotations
|
||||
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class ItemDefinitions
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package org.apollo.game.plugin.testing.junit.api.annotations
|
||||
|
||||
annotation class Id(val value: Int)
|
||||
annotation class Pos(val x: Int, val y: Int, val height: Int = 0)
|
||||
|
||||
@Target(AnnotationTarget.FIELD)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class TestMock
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package org.apollo.game.plugin.testing.junit.api.annotations
|
||||
|
||||
import org.apollo.game.action.Action
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
annotation class ActionTest(val value: KClass<out Action<*>> = Action::class)
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package org.apollo.game.plugin.testing.junit.api.interactions
|
||||
|
||||
import org.apollo.game.message.impl.ItemOptionMessage
|
||||
import org.apollo.game.message.impl.NpcActionMessage
|
||||
import org.apollo.game.message.impl.ObjectActionMessage
|
||||
import org.apollo.game.message.impl.PlayerActionMessage
|
||||
import org.apollo.game.model.Direction
|
||||
import org.apollo.game.model.Position
|
||||
import org.apollo.game.model.entity.Entity
|
||||
import org.apollo.game.model.entity.Npc
|
||||
import org.apollo.game.model.entity.Player
|
||||
import org.apollo.game.model.entity.obj.GameObject
|
||||
|
||||
/**
|
||||
* Send an [ItemOptionMessage] for the given [id], [option], [slot], and [interfaceId], simulating a
|
||||
* player interacting with an item.
|
||||
*/
|
||||
fun Player.interactWithItem(id: Int, option: Int, slot: Int? = null, interfaceId: Int? = null) {
|
||||
send(ItemOptionMessage(option, interfaceId ?: -1, id, slot ?: inventory.slotOf(id)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn a new object (defaulting to in-front of the player) and immediately interact with it.
|
||||
*/
|
||||
fun Player.interactWithObject(id: Int, option: Int, at: Position? = null) {
|
||||
val obj = world.spawnObject(id, at ?: position.step(1, Direction.NORTH))
|
||||
interactWith(obj, option)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the player within interaction distance to the given [Entity] and fake an action
|
||||
* message.
|
||||
*/
|
||||
fun Player.interactWith(entity: Entity, option: Int = 1) {
|
||||
position = entity.position.step(1, Direction.NORTH)
|
||||
|
||||
when (entity) {
|
||||
is GameObject -> send(ObjectActionMessage(option, entity.id, entity.position))
|
||||
is Npc -> send(NpcActionMessage(option, entity.index))
|
||||
is Player -> send(PlayerActionMessage(option, entity.index))
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package org.apollo.game.plugin.testing.junit.api.interactions
|
||||
|
||||
import org.apollo.cache.def.NpcDefinition
|
||||
import org.apollo.game.model.Position
|
||||
import org.apollo.game.model.World
|
||||
import org.apollo.game.model.entity.Npc
|
||||
import org.apollo.game.model.entity.obj.GameObject
|
||||
import org.apollo.game.model.entity.obj.StaticGameObject
|
||||
|
||||
/**
|
||||
* Spawn a new static game object into the world with the given id and position.
|
||||
*/
|
||||
fun World.spawnObject(id: Int, position: Position): GameObject {
|
||||
val obj = StaticGameObject(this, id, position, 0, 0)
|
||||
|
||||
spawn(obj)
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Spawns a new NPC with the minimum set of dependencies required to function correctly in the world.
|
||||
*/
|
||||
fun World.spawnNpc(id: Int, position: Position): Npc {
|
||||
val definition = NpcDefinition(id)
|
||||
val npc = Npc(this, position, definition, arrayOfNulls(4))
|
||||
val region = regionRepository.fromPosition(position)
|
||||
val npcs = npcRepository
|
||||
|
||||
npcs.add(npc)
|
||||
region.addEntity(npc)
|
||||
|
||||
return npc
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package org.apollo.game.plugin.testing.junit.mocking
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
data class StubPrototype<T : Any>(val type: KClass<T>, val annotations: Collection<Annotation>)
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
package org.apollo.game.plugin.testing.junit.stubs
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
package org.apollo.game.plugin.testing.junit.stubs
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package org.apollo.game.plugin.testing.junit.stubs
|
||||
|
||||
import org.apollo.game.model.Position
|
||||
import org.apollo.game.plugin.testing.junit.api.annotations.Pos
|
||||
|
||||
class PlayerStubInfo {
|
||||
companion object {
|
||||
fun create(annotations: Collection<Annotation>): PlayerStubInfo {
|
||||
val info = PlayerStubInfo()
|
||||
|
||||
annotations.forEach {
|
||||
when (it) {
|
||||
is Pos -> info.position = Position(it.x, it.y, it.height)
|
||||
}
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var position = Position(3222, 3222)
|
||||
var name = "test"
|
||||
}
|
||||
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
package org.apollo.game.plugin.testing.mockito
|
||||
|
||||
import org.mockito.ArgumentMatcher
|
||||
import java.lang.AssertionError
|
||||
import java.util.function.Consumer
|
||||
|
||||
class KotlinArgMatcher<T>(val consumer: Consumer<T>) : ArgumentMatcher<T>() {
|
||||
private var error: String? = null
|
||||
|
||||
override fun matches(argument: Any?): Boolean {
|
||||
try {
|
||||
consumer.accept(argument as T)
|
||||
return true
|
||||
} catch (err: AssertionError) {
|
||||
error = err.message
|
||||
println(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return error ?: ""
|
||||
}
|
||||
}
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
package org.apollo.game.plugin.testing.mockito
|
||||
|
||||
import org.mockito.Mockito
|
||||
import java.util.function.Consumer
|
||||
|
||||
|
||||
object KotlinMockitoExtensions {
|
||||
inline fun <reified T> matches(crossinline callback: T.() -> Unit): T {
|
||||
val consumer = Consumer<T> { it.callback() }
|
||||
val matcher = KotlinArgMatcher(consumer)
|
||||
|
||||
return Mockito.argThat(matcher)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user