Add unit test on count up timer
This commit is contained in:
parent
3f6b60c63d
commit
6f18c020ea
|
@ -53,4 +53,12 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation libs.jetbrains.coroutinesAndroid
|
implementation libs.jetbrains.coroutinesAndroid
|
||||||
|
|
||||||
|
// TESTS
|
||||||
|
testImplementation libs.tests.junit
|
||||||
|
testImplementation libs.tests.kluent
|
||||||
|
testImplementation libs.mockk.mockk
|
||||||
|
testImplementation(libs.jetbrains.coroutinesTest) {
|
||||||
|
exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,22 +19,21 @@ package im.vector.lib.core.utils.timer
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.concurrent.atomic.AtomicLong
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
|
||||||
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
|
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
|
||||||
class CountUpTimer(
|
class CountUpTimer(
|
||||||
|
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Main),
|
||||||
private val clock: Clock = DefaultClock(),
|
private val clock: Clock = DefaultClock(),
|
||||||
private val intervalInMs: Long = 1_000,
|
private val intervalInMs: Long = 1_000,
|
||||||
initialTime: Long = 0L,
|
initialTime: Long = 0L,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val coroutineScope = CoroutineScope(Dispatchers.Main)
|
|
||||||
private var counterJob: Job? = null
|
private var counterJob: Job? = null
|
||||||
|
|
||||||
private val lastTime: AtomicLong = AtomicLong()
|
private val lastTime: AtomicLong = AtomicLong(clock.epochMillis())
|
||||||
private val elapsedTime: AtomicLong = AtomicLong(initialTime)
|
private val elapsedTime: AtomicLong = AtomicLong(initialTime)
|
||||||
|
|
||||||
private fun startCounter() {
|
private fun startCounter() {
|
||||||
|
@ -70,7 +69,7 @@ class CountUpTimer(
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
tickListener?.onTick(elapsedTime())
|
tickListener?.onTick(elapsedTime())
|
||||||
coroutineScope.cancel()
|
counterJob?.cancel()
|
||||||
counterJob = null
|
counterJob = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.lib.core.utils.test.fakes
|
||||||
|
|
||||||
|
import im.vector.lib.core.utils.timer.Clock
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
|
||||||
|
class FakeClock : Clock by mockk() {
|
||||||
|
fun givenEpoch(epoch: Long) {
|
||||||
|
every { epochMillis() } returns epoch
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package im.vector.lib.core.utils.timer
|
||||||
|
|
||||||
|
import im.vector.lib.core.utils.test.fakes.FakeClock
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verifySequence
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.advanceTimeBy
|
||||||
|
import kotlinx.coroutines.test.currentTime
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
private const val AN_INTERVAL = 500L
|
||||||
|
private const val AN_INITIAL_TIME = 2_333L
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
internal class CountUpTimerTest {
|
||||||
|
|
||||||
|
private val fakeClock = FakeClock()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when pausing and resuming the timer, the timer ticks the right values at the right moments`() = runTest {
|
||||||
|
every { fakeClock.epochMillis() } answers { currentTime }
|
||||||
|
val tickListener = mockk<CountUpTimer.TickListener>(relaxed = true)
|
||||||
|
val timer = CountUpTimer(
|
||||||
|
coroutineScope = this,
|
||||||
|
clock = fakeClock,
|
||||||
|
intervalInMs = AN_INTERVAL,
|
||||||
|
initialTime = 0,
|
||||||
|
).also { it.tickListener = tickListener }
|
||||||
|
|
||||||
|
timer.resume()
|
||||||
|
advanceTimeBy(AN_INTERVAL / 2) // no tick
|
||||||
|
timer.pause() // tick
|
||||||
|
advanceTimeBy(AN_INTERVAL * 10) // no tick
|
||||||
|
timer.resume() // no tick
|
||||||
|
advanceTimeBy(AN_INTERVAL * 4) // tick * 4
|
||||||
|
timer.stop() // tick
|
||||||
|
|
||||||
|
verifySequence {
|
||||||
|
tickListener.onTick(AN_INTERVAL / 2)
|
||||||
|
tickListener.onTick(AN_INTERVAL)
|
||||||
|
tickListener.onTick(AN_INTERVAL * 2)
|
||||||
|
tickListener.onTick(AN_INTERVAL * 3)
|
||||||
|
tickListener.onTick(AN_INTERVAL * 4)
|
||||||
|
tickListener.onTick(AN_INTERVAL * 4 + AN_INTERVAL / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given an initial time, the timer ticks the right values at the right moments`() = runTest {
|
||||||
|
every { fakeClock.epochMillis() } answers { currentTime }
|
||||||
|
val tickListener = mockk<CountUpTimer.TickListener>(relaxed = true)
|
||||||
|
val timer = CountUpTimer(
|
||||||
|
coroutineScope = this,
|
||||||
|
clock = fakeClock,
|
||||||
|
intervalInMs = AN_INTERVAL,
|
||||||
|
initialTime = AN_INITIAL_TIME,
|
||||||
|
).also { it.tickListener = tickListener }
|
||||||
|
|
||||||
|
timer.resume()
|
||||||
|
advanceTimeBy(AN_INTERVAL) // tick
|
||||||
|
timer.pause() // tick
|
||||||
|
advanceTimeBy(AN_INTERVAL * 10) // no tick
|
||||||
|
timer.resume() // no tick
|
||||||
|
advanceTimeBy(AN_INTERVAL * 4) // tick * 4
|
||||||
|
timer.stop() // tick
|
||||||
|
|
||||||
|
val offset = AN_INITIAL_TIME % AN_INTERVAL
|
||||||
|
verifySequence {
|
||||||
|
tickListener.onTick(AN_INITIAL_TIME + AN_INTERVAL - offset)
|
||||||
|
tickListener.onTick(AN_INITIAL_TIME + AN_INTERVAL)
|
||||||
|
tickListener.onTick(AN_INITIAL_TIME + AN_INTERVAL * 2 - offset)
|
||||||
|
tickListener.onTick(AN_INITIAL_TIME + AN_INTERVAL * 3 - offset)
|
||||||
|
tickListener.onTick(AN_INITIAL_TIME + AN_INTERVAL * 4 - offset)
|
||||||
|
tickListener.onTick(AN_INITIAL_TIME + AN_INTERVAL * 5 - offset)
|
||||||
|
tickListener.onTick(AN_INITIAL_TIME + AN_INTERVAL * 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue