Rewrite all unit tests using kotest

This commit is contained in:
Niels van Velzen 2022-02-11 22:20:21 +01:00 committed by Niels van Velzen
parent 2cfd134a83
commit 22c50d50f5
10 changed files with 199 additions and 217 deletions

View File

@ -67,6 +67,10 @@ android {
sarifReport = true
checkDependencies = true
}
testOptions.unitTests.all {
it.useJUnitPlatform()
}
}
val versionTxt by tasks.registering {
@ -148,6 +152,7 @@ dependencies {
coreLibraryDesugaring(libs.android.desugar)
// Testing
testImplementation(libs.junit)
testImplementation(libs.kotest.runner.junit5)
testImplementation(libs.kotest.assertions)
testImplementation(libs.mockk)
}

View File

@ -0,0 +1,8 @@
# Note: This file is temporarily, these settings should be moved to the top-level editorconfig to
# affect the complete project.
[{*.kts,*.kt}]
# Disable wildcard imports in IntelliJ/Android Studio
ij_kotlin_name_count_to_use_star_import = 1000
ij_kotlin_name_count_to_use_star_import_for_members = 1000
ij_kotlin_packages_to_use_import_on_demand =

View File

@ -1,30 +1,27 @@
package org.jellyfin.androidtv.ui.playback
import io.mockk.*
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Test
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.doubles.plusOrMinus
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.justRun
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
class VideoSpeedControllerTest {
@After
fun tearDown() {
class VideoSpeedControllerTests : FunSpec({
afterTest {
// Always reset the "user selected" speed back to default
VideoSpeedController(mockk(relaxed = true)).resetSpeedToDefault()
}
@Test
fun testSpeedSteps() {
val speedSteps = VideoSpeedController.SpeedSteps.values()
val expectedStep = 0.25
var i = 1
speedSteps.forEach { v ->
assertEquals(i * expectedStep, v.speed, 0.001)
i += 1
test("VideoSpeedController.SpeedSteps uses intervals of 0.25") {
VideoSpeedController.SpeedSteps.values().forEachIndexed { i, v ->
v.speed.shouldBe((i + 1) * 0.25 plusOrMinus 0.0001)
}
}
@Test
fun testControllerSpeedIsOneByDefault() {
test("VideoSpeedController speed is 1 by default") {
val mockController = mockk<PlaybackController>(relaxed = true)
val slot = slot<Double>()
justRun { mockController.setPlaybackSpeed(capture(slot)) }
@ -32,20 +29,20 @@ class VideoSpeedControllerTest {
VideoSpeedController(mockController)
verify { mockController.setPlaybackSpeed(any()) }
assertEquals(1.0, slot.captured, 0.001)
slot.captured shouldBe (1.0 plusOrMinus 0.0001)
}
@Test
fun testSetNewSpeed() {
test("VideoSpeedController.currentSpeed getter returns set value") {
val mockController = mockk<PlaybackController>(relaxed = true)
val controller = VideoSpeedController(mockController)
val expected = VideoSpeedController.SpeedSteps.SPEED_1_25
controller.currentSpeed = expected
assertEquals(expected, controller.currentSpeed)
controller.currentSpeed shouldBe expected
}
@Test
fun testSetNewSpeedSetsOnManager() {
test("VideoSpeedController.currentSpeed updates the speed in the controller") {
val mockController = mockk<PlaybackController>(relaxed = true)
val slot = slot<Double>()
justRun { mockController.setPlaybackSpeed(capture(slot)) }
@ -55,11 +52,10 @@ class VideoSpeedControllerTest {
controller.currentSpeed = expected
verify { mockController.setPlaybackSpeed(any()) }
assertEquals(expected.speed, slot.captured, 0.0001)
slot.captured shouldBe (expected.speed plusOrMinus 0.0001)
}
@Test
fun testResetPreviousSpeedToDefault() {
test("VideoSpeedController.resetSpeedToDefault() works correctly") {
val playbackController = mockk<PlaybackController>(relaxed = true)
val videoController = VideoSpeedController(playbackController)
@ -71,20 +67,13 @@ class VideoSpeedControllerTest {
VideoSpeedController(playbackController)
verify { playbackController.setPlaybackSpeed(any()) }
assertEquals(
VideoSpeedController.SpeedSteps.SPEED_1_00.speed,
slot.captured,
0.0001
)
slot.captured shouldBe (VideoSpeedController.SpeedSteps.SPEED_1_00.speed plusOrMinus 0.0001)
}
@Test
fun testControllerPreservesMostRecentlySelectedSpeedConstructingNew() {
test("VideoSpeedController remembers previous instance speed value") {
var lastSetSpeed = 1.0
val speeds = VideoSpeedController.SpeedSteps.values()
speeds.forEach { newSpeed ->
VideoSpeedController.SpeedSteps.values().forEach { newSpeed ->
val mockController = mockk<PlaybackController>(relaxed = true)
val slot = slot<Double>()
justRun { mockController.setPlaybackSpeed(capture(slot)) }
@ -92,48 +81,48 @@ class VideoSpeedControllerTest {
val controller = VideoSpeedController(mockController)
verify { mockController.setPlaybackSpeed(any()) }
assertEquals(lastSetSpeed, slot.captured, 0.001)
slot.captured shouldBe (lastSetSpeed plusOrMinus 0.0001)
controller.currentSpeed = newSpeed
lastSetSpeed = newSpeed.speed
}
}
@Test
fun testSpeedResetsToOneWithLiveTv() {
test("VideoSpeedController.currentSpeed always sets the speed to 1 for LiveTV") {
// Since handling live TV is more complex, we will simply reset the playback
// speed to 1 so we can't out-run the current buffer.
// Assume the user has pre-set their speed
VideoSpeedController(mockk(relaxed = true)).currentSpeed =
VideoSpeedController.SpeedSteps.SPEED_2_00
VideoSpeedController(mockk(relaxed = true)).currentSpeed = VideoSpeedController.SpeedSteps.SPEED_2_00
// Then they switch to live-tv
val mockController = mockk<PlaybackController>(relaxed = true)
every { mockController.isLiveTv } returns true
val mockController = mockk<PlaybackController>(relaxed = true) {
every { isLiveTv } returns true
}
val speedController = VideoSpeedController(mockController)
assertEquals(VideoSpeedController.SpeedSteps.SPEED_1_00, speedController.currentSpeed)
verify { mockController.setPlaybackSpeed(1.0) }
speedController.currentSpeed shouldBe VideoSpeedController.SpeedSteps.SPEED_1_00
// Try to set it back to other values should be ignored
speedController.currentSpeed = VideoSpeedController.SpeedSteps.SPEED_2_00
assertEquals(VideoSpeedController.SpeedSteps.SPEED_1_00, speedController.currentSpeed)
verify { mockController.setPlaybackSpeed(1.0) }
speedController.currentSpeed shouldBe VideoSpeedController.SpeedSteps.SPEED_1_00
}
@Test
fun testSpeedChangeableOffLiveTv() {
val mockController = mockk<PlaybackController>(relaxed = true)
every { mockController.isLiveTv } returns false
test("VideoSpeedController.currentSpeed alwats sets the requested speed when LiveTV is off") {
val mockController = mockk<PlaybackController>(relaxed = true) {
every { isLiveTv } returns false
}
val speedController = VideoSpeedController(mockController)
assertEquals(VideoSpeedController.SpeedSteps.SPEED_1_00, speedController.currentSpeed)
verify { mockController.setPlaybackSpeed(1.0) }
speedController.currentSpeed shouldBe VideoSpeedController.SpeedSteps.SPEED_1_00
speedController.currentSpeed = VideoSpeedController.SpeedSteps.SPEED_2_00
assertEquals(VideoSpeedController.SpeedSteps.SPEED_2_00, speedController.currentSpeed)
verify { mockController.setPlaybackSpeed(2.0) }
}
}
verify { mockController.setPlaybackSpeed(2.0) }
speedController.currentSpeed shouldBe VideoSpeedController.SpeedSteps.SPEED_2_00
}
})

View File

@ -1,40 +0,0 @@
package org.jellyfin.androidtv.ui.playback.overlay
import io.mockk.every
import io.mockk.mockk
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Test
class CustomSeekProviderTest {
@Test
fun testGetSeekPositions_withSimpleDuration() {
val videoPlayerAdapter = mockk<VideoPlayerAdapter>()
every { videoPlayerAdapter.canSeek() } returns true
every { videoPlayerAdapter.duration } returns 90000L
val customSeekProvider = CustomSeekProvider(videoPlayerAdapter)
val expected = longArrayOf(0L, 30000L, 60000L, 90000L)
val actual = customSeekProvider.seekPositions
assertArrayEquals(expected, actual)
}
@Test
fun testGetSeekPositions_withOddDuration() {
val videoPlayerAdapter = mockk<VideoPlayerAdapter>()
every { videoPlayerAdapter.canSeek() } returns true
every { videoPlayerAdapter.duration } returns 130000L
val customSeekProvider = CustomSeekProvider(videoPlayerAdapter)
val expected = longArrayOf(0L, 30000, 60000, 90000, 120000, 130000)
val actual = customSeekProvider.seekPositions
assertArrayEquals(expected, actual)
}
@Test
fun testGetSeekPositions_withSeekDisabled() {
val videoPlayerAdapter = mockk<VideoPlayerAdapter>()
every { videoPlayerAdapter.canSeek() } returns false
val customSeekProvider = CustomSeekProvider(videoPlayerAdapter)
val actual = customSeekProvider.seekPositions
assertEquals(0, actual.size.toLong())
}
}

View File

@ -0,0 +1,37 @@
package org.jellyfin.androidtv.ui.playback.overlay
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk
class CustomSeekProviderTests : FunSpec({
test("CustomSeekProvider.seekPositions with simple duration") {
val videoPlayerAdapter = mockk<VideoPlayerAdapter> {
every { canSeek() } returns true
every { duration } returns 90000L
}
val customSeekProvider = CustomSeekProvider(videoPlayerAdapter)
customSeekProvider.seekPositions shouldBe arrayOf(0L, 30000L, 60000L, 90000L)
}
test("CustomSeekProvider.seekPositions with odd duration") {
val videoPlayerAdapter = mockk<VideoPlayerAdapter> {
every { canSeek() } returns true
every { duration } returns 130000L
}
val customSeekProvider = CustomSeekProvider(videoPlayerAdapter)
customSeekProvider.seekPositions shouldBe arrayOf(0L, 30000, 60000, 90000, 120000, 130000)
}
test("CustomSeekProvider.seekPositions with seek disabled") {
val videoPlayerAdapter = mockk<VideoPlayerAdapter> {
every { canSeek() } returns false
}
val customSeekProvider = CustomSeekProvider(videoPlayerAdapter)
customSeekProvider.seekPositions.size shouldBe 0
}
})

View File

@ -1,90 +0,0 @@
package org.jellyfin.androidtv.util
import io.mockk.every
import io.mockk.mockkStatic
import io.mockk.unmockkAll
import org.junit.After
import org.junit.Assert.*
import org.junit.Test
class DeviceUtilsTest {
@After
fun tearDown() = unmockkAll()
@Test
fun testBuildModelIsNull() {
// A number of the tests below rely on the assumption
// that Build.MODEL is Unknown in unit tests
assertEquals("Unknown", DeviceUtils.getBuildModel())
}
@Test
fun testMethodsCanHandleNullModel() {
// Methods that implicitly rely on Build.MODEL
val methods = arrayOf(
DeviceUtils::isChromecastWithGoogleTV,
DeviceUtils::isFireTv,
DeviceUtils::isFireTvStickGen1,
DeviceUtils::isFireTvStick4k,
DeviceUtils::isShieldTv,
DeviceUtils::has4kVideoSupport
)
for (method in methods) {
assertFalse(method.toString(), method())
}
}
private fun mockBuildModel(mockedVal: String) {
mockkStatic(DeviceUtils::class)
every { DeviceUtils.getBuildModel() } returns mockedVal
}
@Test
fun testIsChromecastGoogleTvTrue() {
mockBuildModel("Chromecast")
assertTrue(DeviceUtils.isChromecastWithGoogleTV())
}
@Test
fun testIsFireTV() {
val acceptableInputs = arrayOf("AFT", "AFT_foo", "AFT ", "AFT2")
for (input in acceptableInputs) {
mockBuildModel(input)
assertTrue(DeviceUtils.isFireTv())
}
}
@Test
fun testIsFireTVGen1() {
mockBuildModel("AFTM")
assertTrue(DeviceUtils.isFireTv())
assertTrue(DeviceUtils.isFireTvStickGen1())
assertFalse(DeviceUtils.isFireTvStick4k())
}
@Test
fun testIsFireTV4k() {
mockBuildModel("AFTMM")
assertTrue(DeviceUtils.isFireTv())
assertTrue(DeviceUtils.isFireTvStick4k())
assertFalse(DeviceUtils.isFireTvStickGen1())
}
@Test
fun testIsShieldTv() {
mockBuildModel("SHIELD Android TV")
assertTrue(DeviceUtils.isShieldTv())
}
@Test
fun testDisabled4kModels() {
val fire1080pSticks = arrayOf(
"AFTM", "AFTT", "AFTSSS", "AFTSS", "AFTB", "AFTS"
)
for (input in fire1080pSticks) {
mockBuildModel(input)
assertFalse(input, DeviceUtils.has4kVideoSupport())
}
}
}

View File

@ -0,0 +1,73 @@
package org.jellyfin.androidtv.util
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
class DeviceUtilsTests : FunSpec({
test("DeviceUtils.getBuildModel() is unknown") {
DeviceUtils.getBuildModel() shouldBe "Unknown"
}
test("DeviceUtils methods support unknown as model") {
DeviceUtils.isChromecastWithGoogleTV() shouldBe false
DeviceUtils.isFireTv() shouldBe false
DeviceUtils.isFireTvStickGen1() shouldBe false
DeviceUtils.isFireTvStick4k() shouldBe false
DeviceUtils.isShieldTv() shouldBe false
DeviceUtils.has4kVideoSupport() shouldBe false
}
fun withBuildModel(buildModel: String, block: () -> Unit) {
mockkStatic(DeviceUtils::class)
every { DeviceUtils.getBuildModel() } returns buildModel
block()
unmockkStatic(DeviceUtils::class)
}
test("DeviceUtils.isChromecastWithGoogleTV() works correctly") {
withBuildModel("Chromecast") {
DeviceUtils.isChromecastWithGoogleTV() shouldBe true
}
}
test("DeviceUtils.isFireTv() works correctly") {
arrayOf("AFT", "AFT_foo", "AFT ", "AFT2").forEach { input ->
withBuildModel(input) {
DeviceUtils.isFireTv() shouldBe true
}
}
}
test("DeviceUtils.isFireTvStickGen1() works correctly") {
withBuildModel("AFTM") {
DeviceUtils.isFireTv() shouldBe true
DeviceUtils.isFireTvStickGen1() shouldBe true
DeviceUtils.isFireTvStick4k() shouldBe false
}
}
test("DeviceUtils.isFireTvStick4k() works correctly") {
withBuildModel("AFTMM") {
DeviceUtils.isFireTv() shouldBe true
DeviceUtils.isFireTvStick4k() shouldBe true
DeviceUtils.isFireTvStickGen1() shouldBe false
}
}
test("DeviceUtils.isShieldTv() works correctly") {
withBuildModel("SHIELD Android TV") {
DeviceUtils.isShieldTv() shouldBe true
}
}
test("DeviceUtils.has4kVideoSupport() works correctly") {
arrayOf("AFTM", "AFTT", "AFTSSS", "AFTSS", "AFTB", "AFTS").forEach { input ->
withBuildModel(input) {
DeviceUtils.has4kVideoSupport() shouldBe false
}
}
}
})

View File

@ -1,30 +0,0 @@
package org.jellyfin.androidtv.util
import org.junit.Assert.assertEquals
import org.junit.Test
class TimeUtilsTest {
@Test
fun secondsToMillis() {
assertEquals(0, TimeUtils.secondsToMillis(0.0))
assertEquals(1000, TimeUtils.secondsToMillis(1.0))
assertEquals(1250, TimeUtils.secondsToMillis(1.25))
}
@Test
fun formatMillis() {
assertEquals("0:00", TimeUtils.formatMillis(0))
assertEquals("0:13", TimeUtils.formatMillis(13000))
assertEquals("5:00", TimeUtils.formatMillis(300000))
assertEquals("9:01", TimeUtils.formatMillis(541000))
assertEquals("9:59", TimeUtils.formatMillis(599000))
assertEquals("26:00", TimeUtils.formatMillis(1560000))
assertEquals("26:01", TimeUtils.formatMillis(1561000))
assertEquals("26:43", TimeUtils.formatMillis(1603000))
assertEquals("1:00:00", TimeUtils.formatMillis(3600000))
assertEquals("1:01:01", TimeUtils.formatMillis(3661000))
assertEquals("1:09:15", TimeUtils.formatMillis(4155000))
assertEquals("1:16:03", TimeUtils.formatMillis(4563489))
assertEquals("12:00:00", TimeUtils.formatMillis(43200000))
}
}

View File

@ -0,0 +1,28 @@
package org.jellyfin.androidtv.util
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
class TimeUtilsTests : FunSpec({
test("TimeUtils.secondstoMillis() works correctly") {
TimeUtils.secondsToMillis(0.0) shouldBe 0
TimeUtils.secondsToMillis(1.0) shouldBe 1000
TimeUtils.secondsToMillis(1.25) shouldBe 1250
}
test("TimeUtils.formatMillis() works correctly") {
TimeUtils.formatMillis(0) shouldBe "0:00"
TimeUtils.formatMillis(13000) shouldBe "0:13"
TimeUtils.formatMillis(300000) shouldBe "5:00"
TimeUtils.formatMillis(541000) shouldBe "9:01"
TimeUtils.formatMillis(599000) shouldBe "9:59"
TimeUtils.formatMillis(1560000) shouldBe "26:00"
TimeUtils.formatMillis(1561000) shouldBe "26:01"
TimeUtils.formatMillis(1603000) shouldBe "26:43"
TimeUtils.formatMillis(3600000) shouldBe "1:00:00"
TimeUtils.formatMillis(3661000) shouldBe "1:01:01"
TimeUtils.formatMillis(4155000) shouldBe "1:09:15"
TimeUtils.formatMillis(4563489) shouldBe "1:16:03"
TimeUtils.formatMillis(43200000) shouldBe "12:00:00"
}
})

View File

@ -35,6 +35,7 @@ libvlc = "3.4.7"
mockk = "1.12.1"
slf4j-timber = "3.1"
timber = "5.0.1"
kotest = "5.1.0"
[plugins]
aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" }
@ -109,8 +110,9 @@ leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version.re
# Compatibility (desugaring)
android-desugar = { module = "com.android.tools:desugar_jdk_libs", version.ref = "android-desugar" }
# Testing
junit = { module = "junit:junit", version.ref = "junit" }
# Test utilities
kotest-runner-junit5 = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
kotest-assertions = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
[bundles]