Created
September 28, 2022 09:07
-
-
Save delitescere/9a6103be5af6ef3253e4a99ef37801c0 to your computer and use it in GitHub Desktop.
A JUnit5 base test class for Kotlin that includes TestInfo values for data partitioning and Clock manipulation for time-sensitive tests
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package test | |
import org.junit.jupiter.api.BeforeEach | |
import org.junit.jupiter.api.TestInfo | |
import org.junit.jupiter.api.TestInstance | |
import java.time.Clock | |
import java.time.Instant | |
import java.time.ZoneId | |
import java.util.* | |
import kotlin.math.absoluteValue | |
import kotlin.properties.Delegates | |
/** | |
* | |
* To set a clock that doesn't tick for the whole test class (unless a test case makes it tick), do something like this: | |
* | |
* ```kotlin | |
* private val clock: Clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()) | |
* internal class TestSomething : BaseTest(clock) { | |
* } | |
* ``` | |
* The clock will be fixed to the time the test class is statically initialized. | |
* | |
* | |
* or: | |
* | |
* ```kotlin | |
* internal class TestSomething : BaseTest() { | |
* init { epochUtcClock() } | |
* } | |
* ``` | |
* using a helper function when the test class is instantiated, which will be sensitive to whatever [TestInstance.Lifecycle] is in effect. | |
* | |
* or, to ensure the clock is reset before each test case, regardless of Lifecycle: | |
* | |
* ```kotlin | |
* internal class TestSomething : BaseTest() { | |
* @BeforeEach | |
* fun fixClock() = epochUtcClock() | |
* } | |
* ``` | |
* | |
* @param clock A clock (e.g. [Clock.fixed]) for time-sensitive testing. Defaults to the "normal" clock [Clock.systemDefaultZone]. | |
* Helper functions can set this clock for each test. It is reset to the originally specificied clock before each test. | |
* @property testId A positive integer used for data partitioning for each test case. | |
*/ | |
@Suppress("MemberVisibilityCanBePrivate") | |
abstract class BaseTest(clock: Clock? = null) { | |
private val _originalClock: Clock = clock ?: Clock.systemDefaultZone() | |
private var _clock = _originalClock | |
private var _testNow = Instant.now(_clock) | |
private var _testId by Delegates.notNull<Int>() | |
private var _testName by Delegates.notNull<String>() | |
/** | |
* A deterministic numerical identity for the test case. It will be a low positive integer, and stable for as long as the following remain unchanged: | |
* - The test class name | |
* - The test case name (i.e. the test method name) | |
* - The number of test cases in a test class | |
*/ | |
protected val testId | |
get() = _testId | |
protected val testName | |
get() = _testName | |
protected val testNow | |
get() = _testNow | |
protected val clock | |
get() = _clock | |
@BeforeEach | |
fun useTestInfo(info: TestInfo) { | |
_testId = (info.testMethod.get().hashCode() % (info.testClass.get().methods.size)).absoluteValue + 1 | |
_testName = upperCamelCase(info.testMethod.get().name) | |
} | |
private fun upperCamelCase(s: String): String { | |
val words = s.split(Regex("[\\W_]+")) | |
val builder = StringBuilder() | |
for (i in words.indices) { | |
val word: String = words[i] | |
builder.append(word[0].uppercaseChar().toString() + word.substring(1).lowercase(Locale.getDefault())) | |
} | |
return builder.toString() | |
} | |
/** | |
* Fix the clock to the current time in UTC | |
*/ | |
protected fun utcClock() { | |
_clock = Clock.fixed(Instant.now(_originalClock), ZoneId.of("Z")) | |
} | |
/** | |
* Fix the clock to Epoch in the current zone | |
*/ | |
protected fun epochClock() { | |
_clock = Clock.fixed(Instant.EPOCH, _originalClock.zone) | |
} | |
/** | |
* Fix the clock to the current time in the current zone | |
*/ | |
protected fun fixedClock() { | |
_clock = Clock.fixed(Instant.now(_originalClock), _originalClock.zone) | |
} | |
/** | |
* Fix the clock to Epoch in UTC | |
*/ | |
protected fun epochUtcClock() { | |
_clock = Clock.fixed(Instant.EPOCH, ZoneId.of("Z")) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment