Allow multiple players per test

Refactors the test helpers to use receiver functions so multiple players
can exist in the world per test case.  The AddFriendsTest is an example
of where this is needed.

Additionally, Hamcrest has been replaced with AssertJ for fluent
assertions.
This commit is contained in:
Gary Tierney
2017-06-20 02:13:47 +01:00
parent b532168551
commit 182de0330f
15 changed files with 254 additions and 186 deletions
+1 -3
View File
@@ -33,9 +33,7 @@ dependencies {
compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jre8', version: "$kotlinVersion"
compile group: 'org.jetbrains.kotlin', name: 'kotlin-compiler-embeddable', version: "$kotlinVersion"
// https://mvnrepository.com/artifact/org.hamcrest/hamcrest-all
testCompile group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3'
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlinVersion"
testCompile group: 'org.assertj', name: 'assertj-core', version: '3.8.0'
}
task run(type: JavaExec, dependsOn: classes) {
@@ -27,7 +27,7 @@ public final class SendFriendMessage extends Message {
*/
public SendFriendMessage(String username, int world) {
this.username = username;
this.world = world == 0 ? 0 : world + 9;
this.world = world;
}
/**
@@ -48,4 +48,12 @@ public final class SendFriendMessage extends Message {
return world;
}
/**
* Gets the encoded world id to be sent to the client.
*
* @return The encoded world id.
*/
public int getEncodedWorld() {
return world == 0 ? 0 : world + 9;
}
}
@@ -18,7 +18,7 @@ public final class SendFriendMessageEncoder extends MessageEncoder<SendFriendMes
public GamePacket encode(SendFriendMessage message) {
GamePacketBuilder builder = new GamePacketBuilder(50);
builder.put(DataType.LONG, NameUtil.encodeBase37(message.getUsername()));
builder.put(DataType.BYTE, message.getWorld());
builder.put(DataType.BYTE, message.getEncodedWorld());
return builder.toGamePacket();
}
@@ -18,7 +18,7 @@ public final class SendFriendMessageEncoder extends MessageEncoder<SendFriendMes
public GamePacket encode(SendFriendMessage message) {
GamePacketBuilder builder = new GamePacketBuilder(78);
builder.put(DataType.LONG, NameUtil.encodeBase37(message.getUsername()));
builder.put(DataType.BYTE, message.getWorld());
builder.put(DataType.BYTE, message.getEncodedWorld());
return builder.toGamePacket();
}
@@ -0,0 +1,34 @@
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
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")
}
}
@@ -0,0 +1,111 @@
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()
} 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))
}
}
}
@@ -0,0 +1,21 @@
package org.apollo.game.plugin.testing.fakes
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<*>)
}
return PowerMockito.mock(PluginContext::class.java, answer)
}
}
@@ -0,0 +1,25 @@
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 ?: ""
}
}
@@ -0,0 +1,14 @@
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)
}
}
@@ -1,113 +0,0 @@
package org.apollo.game.plugins.testing
import org.apollo.cache.def.NpcDefinition
import org.apollo.game.action.Action
import org.apollo.game.message.handler.MessageHandler
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.game.plugin.*
import org.apollo.net.message.Message
import org.apollo.util.security.PlayerCredentials
import org.junit.Assert
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Matchers.any
import org.mockito.Mockito
import org.mockito.invocation.InvocationOnMock
import org.mockito.stubbing.Answer
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 {
lateinit var world: World
lateinit var player: Player
lateinit var messageHandlers: MessageHandlerChainSet
@Before
fun setup() {
messageHandlers = MessageHandlerChainSet()
world = PowerMockito.spy(World())
val answer = Answer<Any?> { invocation: InvocationOnMock ->
messageHandlers.putHandler(
invocation.arguments[0] as Class<Message>,
invocation.arguments[1] as MessageHandler<*>)
}
val pluginContext = PowerMockito.mock<PluginContext>(PluginContext::class.java, answer)
val pluginEnvironment = KotlinPluginEnvironment(world)
pluginEnvironment.setContext(pluginContext)
pluginEnvironment.load(ArrayList<PluginMetaData>())
val credentials = PlayerCredentials("test", "test", 1, 1, "0.0.0.0")
val position = Position(3200, 3200, 0)
val region = world.regionRepository.fromPosition(position)
player = PowerMockito.spy(Player(world, credentials, position))
world.register(player)
region.addEntity(player)
PowerMockito.doNothing().`when`(player).send(any())
}
fun interactWith(entity: Entity, option: Int = 1) {
player.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))
}
}
fun spawnNpc(id: Int, position: Position): Npc {
val definition = NpcDefinition(id)
val npc = Npc(world, position, definition, arrayOfNulls(4))
val region = world.regionRepository.fromPosition(position)
val npcs = world.npcRepository
npcs.add(npc)
region.addEntity(npc)
return npc
}
fun spawnObject(id: Int, position: Position): GameObject {
val obj = StaticGameObject(world, id, position, 0, 0)
world.spawn(obj)
return obj
}
fun notify(message: Message) {
messageHandlers.notify(player, message)
}
fun waitForActionCompletion(predicate: (Action<Player>) -> Boolean = { _ -> true }, timeout: Int = 15) {
val actionCaptor: ArgumentCaptor<Action<*>> = ArgumentCaptor.forClass(Action::class.java)
Mockito.verify(player).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()
} while (action.isRunning && pulses++ < timeout)
Assert.assertFalse("Exceeded timeout waiting for action completion", pulses > timeout)
}
}
+7 -7
View File
@@ -1,5 +1,5 @@
import org.apollo.game.model.Position
import org.apollo.game.plugins.testing.KotlinPluginTest
import org.apollo.game.plugin.testing.KotlinPluginTest
import org.junit.Test
import org.mockito.Mockito.verify
@@ -14,21 +14,21 @@ class OpenBankTest() : KotlinPluginTest() {
@Test
fun `Interacting with a bank teller should open the players bank`() {
val bankTeller = spawnNpc(BANK_TELLER_ID, BANK_POSITION)
val bankTeller = world.spawnNpc(BANK_TELLER_ID, BANK_POSITION)
// @todo - these option numbers only match by coincidence, we should be looking up the correct ones
interactWith(bankTeller, option = 2)
waitForActionCompletion()
player.interactWith(bankTeller, option = 2)
player.waitForActionCompletion()
verify(player).openBank()
}
@Test
fun `Interacting with a bank booth object should open the players bank`() {
val bankBooth = spawnObject(BANK_BOOTH_ID, BANK_POSITION)
val bankBooth = world.spawnObject(BANK_BOOTH_ID, BANK_POSITION)
interactWith(bankBooth, option = 2)
waitForActionCompletion()
player.interactWith(bankBooth, option = 2)
player.waitForActionCompletion()
verify(player).openBank()
}
@@ -3,21 +3,19 @@ import org.apollo.game.message.impl.SendFriendMessage
import org.apollo.game.model.entity.setting.PrivacyState
on { AddFriendMessage::class }
.then {
it.addFriend(username)
.then {
it.addFriend(username)
val playerUsername = it.username
val friend = it.world.getPlayer(username)
val friend = it.world.getPlayer(username)
if (friend == null) {
it.send(SendFriendMessage(username, 0))
} else if (friend.friendsWith(playerUsername) || friend.friendPrivacy == PrivacyState.ON) {
if (it.friendPrivacy != PrivacyState.OFF) {
friend.send(SendFriendMessage(playerUsername, it.worldId))
}
if (friend.friendPrivacy != PrivacyState.OFF) {
it.send(SendFriendMessage(username, friend.worldId))
}
}
if (friend == null || friend.friendPrivacy == PrivacyState.OFF) {
it.send(SendFriendMessage(username, 0))
return@then
} else {
it.send(SendFriendMessage(username, friend.worldId))
}
if (friend.friendsWith(it.username) && it.friendPrivacy != PrivacyState.OFF) {
friend.send(SendFriendMessage(it.username, it.worldId))
}
}
@@ -1,10 +1,10 @@
import org.apollo.game.model.Position
import org.apollo.game.model.entity.Skill
import org.apollo.game.plugins.testing.KotlinPluginTest
import org.hamcrest.Matchers.*
import org.junit.Assert.*
import org.apollo.game.plugin.testing.*
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.mockito.Mockito.*
import org.mockito.Mockito.contains
import org.mockito.Mockito.verify
class TrainingDummyTest : KotlinPluginTest() {
@@ -14,30 +14,30 @@ class TrainingDummyTest : KotlinPluginTest() {
}
@Test fun `Hitting the training dummy should give the player attack experience`() {
val dummy = spawnObject(DUMMY_ID, DUMMY_POSITION)
val dummy = world.spawnObject(DUMMY_ID, DUMMY_POSITION)
val skills = player.skillSet
val beforeExp = skills.getExperience(Skill.ATTACK)
interactWith(dummy, option = 2)
waitForActionCompletion()
player.interactWith(dummy, option = 2)
player.waitForActionCompletion()
val afterExp = skills.getExperience(Skill.ATTACK)
assertThat(afterExp, greaterThan(beforeExp))
assertThat(afterExp).isGreaterThan(beforeExp)
}
@Test fun `The player should stop getting attack experience from the training dummy at level 8`() {
val dummy = spawnObject(DUMMY_ID, DUMMY_POSITION)
val dummy = world.spawnObject(DUMMY_ID, DUMMY_POSITION)
val skills = player.skillSet
skills.setMaximumLevel(Skill.ATTACK, 8)
val beforeExp = skills.getExperience(Skill.ATTACK)
interactWith(dummy, option = 2)
waitForActionCompletion()
player.interactWith(dummy, option = 2)
player.waitForActionCompletion()
val afterExp = skills.getExperience(Skill.ATTACK)
verify(player).sendMessage(contains("nothing more you can learn"))
assertThat(afterExp, equalTo(beforeExp))
assertThat(afterExp).isEqualTo(beforeExp)
}
}
}
-29
View File
@@ -1,29 +0,0 @@
/**
* NOTE: This file is a stub, intended only for use within an IDE. It should be updated
* each time [org.apollo.game.plugin.kotlin.KotlinPluginScript] has a new method added to it.
*
* Until IntelliJ IDEA starts to support ScriptTemplateDefinitions this is
* required to resolve references within plugin code.
*/
import org.apollo.game.model.World
import org.apollo.game.plugin.PluginContext
import org.apollo.game.plugin.kotlin.*
import org.apollo.net.message.Message
import kotlin.reflect.KClass
fun <T : Message> on(type: () -> KClass<T>): KotlinMessageHandler<T> {
null!!
}
fun on_command(command: String, privileges: PrivilegeLevel): KotlinCommandHandler {
null!!
}
fun start(callback: (World) -> Unit) {
}
fun stop(callback: (World) -> Unit) {
}
@@ -1,14 +1,15 @@
import org.apollo.cache.def.ItemDefinition
import org.apollo.game.plugin.testing.*
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import kotlin.test.assertEquals
class LookupTests {
class LookupTests : KotlinPluginTest() {
@Test fun itemLookup() {
val testItem = ItemDefinition(0)
testItem.name = "sword"
ItemDefinition.init(arrayOf(testItem))
assertEquals(testItem, lookup_item("sword"))
assertThat(lookup_item("sword")).isEqualTo(testItem);
}
}