Skip to content

Commit

Permalink
Create local unit tests in app module (#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
JordanLongstaff authored Jan 9, 2025
1 parent 18f3c34 commit f4db590
Show file tree
Hide file tree
Showing 18 changed files with 820 additions and 4 deletions.
6 changes: 6 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import com.android.build.gradle.internal.tasks.factory.dependsOn
plugins {
id("com.android.application")
kotlin("android")
kotlin("plugin.serialization")
alias(libs.plugins.google.services)
alias(libs.plugins.crashlytics)
alias(libs.plugins.protobuf)
Expand Down Expand Up @@ -42,6 +43,7 @@ android {
kotlinOptions { jvmTarget = javaVersion.toString() }

testOptions.execution = "ANDROIDX_TEST_ORCHESTRATOR"
testOptions.unitTests.all { it.useJUnitPlatform() }

buildTypes {
configureEach {
Expand Down Expand Up @@ -88,6 +90,10 @@ dependencies {

implementation(libs.bundles.app)
debugImplementation(libs.bundles.app.debug)

testImplementation(libs.bundles.app.test)
testRuntimeOnly(libs.bundles.app.test.runtime)

androidTestImplementation(libs.bundles.app.androidTest)
androidTestUtil(libs.test.orchestrator)

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@
<string name="reason_commandeered">liberate commandeered ship</string>
<string name="reason_has_energy">receive energy</string>
<string name="reason_hostage">rescue hostages</string>
<string name="reason_malfunction">reset broken computer</string>
<string name="reason_malfunction">reset computer</string>
<string name="reason_needs_damcon">transfer DamCon personnel</string>
<string name="reason_needs_energy">deliver energy</string>
<string name="reason_pirate_boss">pick up boss</string>
Expand Down
61 changes: 61 additions & 0 deletions app/src/test/kotlin/artemis/agent/cpu/HailResponseEffectTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package artemis.agent.cpu

import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.booleans.shouldBeFalse
import io.kotest.matchers.equals.shouldBeEqual
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream

@OptIn(ExperimentalSerializationApi::class)
class HailResponseEffectTest :
DescribeSpec({
val (testData, ignored) =
HailResponseEffect::class.java.getResourceAsStream("hail-responses.json")!!.use {
Json.decodeFromStream<HailResponseEffectTestData>(it)
}

val testDataMap = testData.groupBy { it.response } - HailResponseEffect.OTHER

val allMessages = testData.flatMap { it.messages }

describe("HailResponseEffect") {
describe("Applies to message") {
testData.forEach { (response) ->
val data = testDataMap[response] ?: return@forEach
describe(response.name) {
allMessages.forEach { message ->
val expected = data.any { it.messages.contains(message) }
listOf(message, message.hasEnergy()).forEach { (long, short) ->
it("$short: $expected") {
response.appliesTo(long) shouldBeEqual expected
}
}
}
}
}
}

describe("Ignored messages") {
ignored
.flatMap { listOf(it, it.hasEnergy()) }
.forEach { (long, short) ->
describe(short) {
HailResponseEffect.entries.forEach { response ->
it(response.name) { response.appliesTo(long).shouldBeFalse() }
}
}
}
}

describe("Ally status") {
testData.forEach { (response, status, messages) ->
describe(status.name) {
messages.forEach { (long, short) ->
it(short) { response.getAllyStatus(long) shouldBeEqual status }
}
}
}
}
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package artemis.agent.cpu

import artemis.agent.game.allies.AllyStatus
import kotlinx.serialization.Serializable

@Serializable
data class HailResponseEffectTestData(
val data: List<HailResponseEffectTestCase>,
val ignored: List<HailResponseEffectTestMessage>,
)

@Serializable
data class HailResponseEffectTestCase(
val response: HailResponseEffect,
val status: AllyStatus = enumValueOf(response.name),
val messages: Set<HailResponseEffectTestMessage>,
)

@Serializable
data class HailResponseEffectTestMessage(val long: String, val short: String) {
override fun toString(): String = long

override fun hashCode(): Int = long.hashCode()

override fun equals(other: Any?): Boolean =
other is HailResponseEffectTestMessage && other.long == long

fun hasEnergy(): HailResponseEffectTestMessage =
HailResponseEffectTestMessage(long + HAS_ENERGY, short + HAS_ENERGY_SHORT)

private companion object {
const val HAS_ENERGY_SHORT = "..need some."
const val HAS_ENERGY = " We also have energy to spare, if you need some."
}
}
42 changes: 42 additions & 0 deletions app/src/test/kotlin/artemis/agent/game/allies/AllyStatusTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package artemis.agent.game.allies

import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.equals.shouldBeEqual
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream

@OptIn(ExperimentalSerializationApi::class)
class AllyStatusTest :
DescribeSpec({
val testCases =
AllyStatus::class.java.getResourceAsStream("ally-statuses.json")!!.use {
Json.decodeFromStream<List<AllyStatusTestCase>>(it)
}

describe("AllyStatus") {
describe("Sort index") {
testCases.forEach { test ->
val status = test.allyStatus
val expectedSortIndex = test.expectedSortIndex
it("$status: $expectedSortIndex") {
status.sortIndex shouldBeEqual expectedSortIndex
}
}
}

describe("Pirate-sensitive equivalents") {
listOf("Is pirate" to true, "Is not pirate" to false).forEach { (name, isPirate) ->
describe(name) {
testCases.forEach { test ->
val expected = test.getStatusForPirate(isPirate)
it("${test.allyStatus} -> $expected") {
test.allyStatus.getPirateSensitiveEquivalent(isPirate) shouldBeEqual
expected
}
}
}
}
}
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package artemis.agent.game.allies

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class AllyStatusTestCase(
@SerialName("status") val allyStatus: AllyStatus,
@SerialName("pirate") val ifPirate: AllyStatus = allyStatus,
@SerialName("notPirate") val ifNotPirate: AllyStatus = allyStatus,
@SerialName("sortIndex") val expectedSortIndex: AllySortIndex = enumValueOf(ifNotPirate.name),
) {
fun getStatusForPirate(isPirate: Boolean) = if (isPirate) ifPirate else ifNotPirate
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package artemis.agent.game.biomechs

import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.equals.shouldBeEqual

class BiomechRageStatusTest :
DescribeSpec({
val expectedStatuses =
arrayOf(
BiomechRageStatus.NEUTRAL,
BiomechRageStatus.HOSTILE,
BiomechRageStatus.HOSTILE,
BiomechRageStatus.HOSTILE,
BiomechRageStatus.HOSTILE,
)

describe("BiomechRageStatus") {
describe("Status from rage") {
expectedStatuses.forEachIndexed { index, status ->
it("$index = $status") { BiomechRageStatus[index] shouldBeEqual status }
}
}
}
})
95 changes: 95 additions & 0 deletions app/src/test/kotlin/artemis/agent/game/enemies/EnemyEntryTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package artemis.agent.game.enemies

import android.content.Context
import androidx.core.content.ContextCompat
import artemis.agent.R
import com.walkertribe.ian.util.BoolState
import com.walkertribe.ian.vesseldata.Faction
import com.walkertribe.ian.vesseldata.Vessel
import com.walkertribe.ian.world.ArtemisNpc
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.equals.shouldBeEqual
import io.kotest.property.Arb
import io.kotest.property.arbitrary.int
import io.kotest.property.checkAll
import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic

class EnemyEntryTest :
DescribeSpec({
val cannotTaunt = "Cannot taunt"
val tauntCountStrings =
arrayOf(
R.string.taunts_zero to "Has not been taunted",
R.string.taunts_one to "Taunted once",
R.string.taunts_two to "Taunted twice",
)

val contextStrings = tauntCountStrings.toMap() + R.string.cannot_taunt.to(cannotTaunt)

val mockContext =
mockk<Context> {
contextStrings.forEach { (key, string) -> every { getString(key) } returns string }
every { getString(R.string.taunts_many, *varargAny { nArgs == 1 }) } answers
{
"Taunted ${lastArg<Array<Any?>>().joinToString()} times"
}
}

mockkStatic(ContextCompat::getColor)
every { ContextCompat.getColor(any(), any()) } answers { lastArg() }

afterSpec {
clearAllMocks()
unmockkStatic(ContextCompat::getColor)
}

fun create(): EnemyEntry = EnemyEntry(ArtemisNpc(0, 0L), mockk<Vessel>(), mockk<Faction>())

describe("EnemyEntry") {
describe("Taunt count text") {
it(cannotTaunt) {
val enemy = create()
enemy.tauntStatuses.fill(TauntStatus.INEFFECTIVE)
enemy.getTauntCountText(mockContext) shouldBeEqual cannotTaunt
}

tauntCountStrings.forEachIndexed { count, (_, string) ->
it(string) {
val enemy = create()
enemy.tauntCount = count
enemy.getTauntCountText(mockContext) shouldBeEqual string
}
}

it("Taunted <count> times") {
val enemy = create()
Arb.int(min = 3).checkAll { count ->
enemy.tauntCount = count
enemy.getTauntCountText(mockContext) shouldBeEqual "Taunted $count times"
}
}
}

describe("Color") {
val enemy = create()

it("Normal: red") {
enemy.getBackgroundColor(mockContext) shouldBeEqual R.color.enemyRed
}

it("Surrendered: yellow") {
enemy.enemy.isSurrendered.value = BoolState.True
enemy.getBackgroundColor(mockContext) shouldBeEqual R.color.surrenderedYellow
}

it("Duplicitous: orange") {
enemy.captainStatus = EnemyCaptainStatus.DUPLICITOUS
enemy.getBackgroundColor(mockContext) shouldBeEqual R.color.duplicitousOrange
}
}
}
})
50 changes: 50 additions & 0 deletions app/src/test/kotlin/artemis/agent/game/misc/AudioEntryTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package artemis.agent.game.misc

import com.walkertribe.ian.enums.AudioCommand
import com.walkertribe.ian.protocol.core.comm.AudioCommandPacket
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.equals.shouldBeEqual
import io.kotest.property.Arb
import io.kotest.property.arbitrary.int
import io.kotest.property.arbitrary.string
import io.kotest.property.checkAll

class AudioEntryTest :
DescribeSpec({
suspend fun testAudioEntry(test: (AudioEntry, Int, String) -> Unit) {
checkAll(Arb.int(), Arb.string()) { audioId, title ->
test(AudioEntry(audioId, title), audioId, title)
}
}

suspend fun testPacketType(
command: AudioCommand,
getPacket: AudioEntry.() -> AudioCommandPacket,
) {
testAudioEntry { entry, audioId, _ ->
val packet = entry.getPacket()
packet.audioId shouldBeEqual audioId
packet.command shouldBeEqual command
}
}

describe("AudioEntry") {
describe("Properties") {
it("Audio ID") {
testAudioEntry { entry, audioId, _ -> entry.audioId shouldBeEqual audioId }
}

it("Title") {
testAudioEntry { entry, _, title -> entry.title shouldBeEqual title }
}

it("Hash code") {
testAudioEntry { entry, audioId, _ -> entry.hashCode() shouldBeEqual audioId }
}
}

it("Play packet") { testPacketType(AudioCommand.PLAY) { playPacket } }

it("Dismiss packet") { testPacketType(AudioCommand.DISMISS) { dismissPacket } }
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package artemis.agent.game.misc

import com.walkertribe.ian.util.JamCrc
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.equals.shouldBeEqual
import io.kotest.property.Arb
import io.kotest.property.arbitrary.string
import io.kotest.property.checkAll

class CommsActionEntryTest :
DescribeSpec({
suspend fun testCommsActionEntry(test: (CommsActionEntry, String) -> Unit) {
Arb.string().checkAll { label -> test(CommsActionEntry(label), label) }
}

describe("CommsActionEntry") {
describe("Properties") {
it("Label") {
testCommsActionEntry { entry, label -> entry.label shouldBeEqual label }
}

it("Hash code") {
testCommsActionEntry { entry, label ->
entry.hashCode() shouldBeEqual JamCrc.compute(label)
}
}
}
}
})
Loading

0 comments on commit f4db590

Please sign in to comment.