Skip to content

Commit ebc2dc0

Browse files
authored
Merge pull request #9812 from wmontwe/test-increase-coverage
test: add tests to reach coverage in some modules
2 parents 37d047a + 6cb0ce5 commit ebc2dc0

File tree

6 files changed

+405
-0
lines changed

6 files changed

+405
-0
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package net.thunderbird.core.architecture.model
2+
3+
import assertk.assertThat
4+
import assertk.assertions.isEqualTo
5+
import assertk.assertions.isNotEqualTo
6+
import kotlin.test.Test
7+
import kotlin.uuid.ExperimentalUuidApi
8+
import kotlin.uuid.Uuid
9+
10+
private class TestTag
11+
12+
@OptIn(ExperimentalUuidApi::class)
13+
private object TestIdFactory : BaseIdFactory<TestTag>()
14+
15+
@OptIn(ExperimentalUuidApi::class)
16+
class BaseIdFactoryTest {
17+
18+
@Test
19+
fun `given raw UUID when of is called then returns Id wrapping parsed UUID`() {
20+
// Arrange
21+
val raw = "123e4567-e89b-12d3-a456-426655440000"
22+
23+
// Act
24+
val id = TestIdFactory.of(raw)
25+
26+
// Assert
27+
assertThat(id.value).isEqualTo(Uuid.parse(raw))
28+
assertThat(id.asRaw()).isEqualTo(raw)
29+
}
30+
31+
@Test
32+
fun `given create is called twice then returns different Ids`() {
33+
// Arrange + Act
34+
val id1 = TestIdFactory.create()
35+
val id2 = TestIdFactory.create()
36+
37+
// Assert
38+
assertThat(id1).isNotEqualTo(id2)
39+
assertThat(id1.asRaw()).isNotEqualTo(id2.asRaw())
40+
}
41+
42+
@Test
43+
fun `given Id created when of is called with its raw then same Id is returned`() {
44+
// Arrange
45+
val original = TestIdFactory.create()
46+
val raw = original.asRaw()
47+
48+
// Act
49+
val parsed = TestIdFactory.of(raw)
50+
51+
// Assert
52+
assertThat(parsed).isEqualTo(Id<TestTag>(Uuid.parse(raw)))
53+
assertThat(parsed).isEqualTo(original)
54+
}
55+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package net.thunderbird.core.architecture.model
2+
3+
import assertk.assertThat
4+
import assertk.assertions.isEqualTo
5+
import assertk.assertions.isNotEqualTo
6+
import kotlin.test.Test
7+
import kotlin.uuid.ExperimentalUuidApi
8+
import kotlin.uuid.Uuid
9+
10+
class IdTestTag
11+
12+
@OptIn(ExperimentalUuidApi::class)
13+
class IdTest {
14+
15+
@Test
16+
fun `given raw UUID when creating Id then asRaw returns same string and value matches`() {
17+
// Arrange
18+
val raw = "123e4567-e89b-12d3-a456-426655440000"
19+
val uuid = Uuid.parse(raw)
20+
21+
// Act
22+
val id = Id<IdTestTag>(uuid)
23+
24+
// Assert
25+
assertThat(id.value).isEqualTo(uuid)
26+
assertThat(id.asRaw()).isEqualTo(raw)
27+
}
28+
29+
@Test
30+
fun `given two Ids with same UUID then they are equal and have same hashCode`() {
31+
// Arrange
32+
val uuid = Uuid.parse("123e4567-e89b-12d3-a456-426655440000")
33+
34+
// Act
35+
val id1 = Id<IdTestTag>(uuid)
36+
val id2 = Id<IdTestTag>(uuid)
37+
38+
// Assert
39+
assertThat(id1).isEqualTo(id2)
40+
assertThat(id1.hashCode()).isEqualTo(id2.hashCode())
41+
}
42+
43+
@Test
44+
fun `given two Ids with different UUIDs then they are not equal`() {
45+
// Arrange
46+
val id1 = Id<IdTestTag>(Uuid.parse("123e4567-e89b-12d3-a456-426655440000"))
47+
val id2 = Id<IdTestTag>(Uuid.parse("123e4567-e89b-12d3-a456-426655440001"))
48+
49+
// Assert
50+
assertThat(id1).isNotEqualTo(id2)
51+
assertThat(id1.asRaw()).isNotEqualTo(id2.asRaw())
52+
}
53+
}

core/featureflag/src/commonTest/kotlin/net/thunderbird/core/featureflag/FeatureFlagResultTest.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,22 @@ class FeatureFlagResultTest {
139139
)
140140
assertThat(unavailableResult).isEqualTo("Feature is OFF")
141141
}
142+
143+
@Test
144+
fun `isEnabled, isDisabled, isUnavailable, isDisabledOrUnavailable should return correct values`() {
145+
assertThat(FeatureFlagResult.Enabled.isEnabled()).isEqualTo(true)
146+
assertThat(FeatureFlagResult.Enabled.isDisabled()).isEqualTo(false)
147+
assertThat(FeatureFlagResult.Enabled.isUnavailable()).isEqualTo(false)
148+
assertThat(FeatureFlagResult.Enabled.isDisabledOrUnavailable()).isEqualTo(false)
149+
150+
assertThat(FeatureFlagResult.Disabled.isEnabled()).isEqualTo(false)
151+
assertThat(FeatureFlagResult.Disabled.isDisabled()).isEqualTo(true)
152+
assertThat(FeatureFlagResult.Disabled.isUnavailable()).isEqualTo(false)
153+
assertThat(FeatureFlagResult.Disabled.isDisabledOrUnavailable()).isEqualTo(true)
154+
155+
assertThat(FeatureFlagResult.Unavailable.isEnabled()).isEqualTo(false)
156+
assertThat(FeatureFlagResult.Unavailable.isDisabled()).isEqualTo(false)
157+
assertThat(FeatureFlagResult.Unavailable.isUnavailable()).isEqualTo(true)
158+
assertThat(FeatureFlagResult.Unavailable.isDisabledOrUnavailable()).isEqualTo(true)
159+
}
142160
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package net.thunderbird.core.outcome
2+
3+
import assertk.assertThat
4+
import assertk.assertions.isEqualTo
5+
import assertk.assertions.isFalse
6+
import assertk.assertions.isNull
7+
import assertk.assertions.isTrue
8+
import kotlin.test.Test
9+
import kotlinx.coroutines.test.runTest
10+
11+
class OutcomeTest {
12+
13+
@Test
14+
fun `given Success when checking then has correct flags and data`() {
15+
// Arrange
16+
val outcome: Outcome<Int, String> = Outcome.success(42)
17+
18+
// Act + Assert
19+
assertThat(outcome.isSuccess).isTrue()
20+
assertThat(outcome.isFailure).isFalse()
21+
val data = (outcome as Outcome.Success).data
22+
assertThat(data).isEqualTo(42)
23+
}
24+
25+
@Test
26+
fun `given Failure when checking then has correct flags and error`() {
27+
// Arrange
28+
val outcome: Outcome<Int, String> = Outcome.failure("error")
29+
30+
// Act + Assert
31+
assertThat(outcome.isFailure).isTrue()
32+
assertThat(outcome.isSuccess).isFalse()
33+
val error = (outcome as Outcome.Failure).error
34+
assertThat(error).isEqualTo("error")
35+
}
36+
37+
@Test
38+
fun `given Success when map is called then transforms success value`() {
39+
// Arrange
40+
val outcome: Outcome<Int, String> = Outcome.Success(7)
41+
42+
// Act
43+
val mapped = outcome.map(
44+
transformSuccess = { it * 2 },
45+
transformFailure = { err, _ -> "$err!" },
46+
)
47+
48+
// Assert
49+
val data = (mapped as Outcome.Success).data
50+
assertThat(data).isEqualTo(14)
51+
}
52+
53+
@Test
54+
fun `given Failure with cause when map is called then transforms failure value and provides cause`() {
55+
// Arrange
56+
val cause = IllegalStateException("cause")
57+
val outcome: Outcome<Int, String> = Outcome.Failure("error", cause)
58+
59+
// Act
60+
val mapped = outcome.map(
61+
transformSuccess = { it * 2 },
62+
transformFailure = { err, receivedCause ->
63+
assertThat(receivedCause).isEqualTo(cause)
64+
"$err-transformed"
65+
},
66+
)
67+
68+
// Assert
69+
val failure = (mapped as Outcome.Failure)
70+
assertThat(failure.error).isEqualTo("error-transformed")
71+
}
72+
73+
@Test
74+
fun `given Success and Failure when mapSuccess is called then only success is transformed`() {
75+
// Arrange & Act
76+
val success = Outcome.Success(3).mapSuccess { it + 1 }
77+
val failure = Outcome.Failure("failure").mapSuccess { 999 }
78+
79+
// Assert
80+
assertThat((success as Outcome.Success).data).isEqualTo(4)
81+
// Failure must be unchanged
82+
assertThat((failure as Outcome.Failure).error).isEqualTo("failure")
83+
}
84+
85+
@Test
86+
fun `given Success and Failure when flatMapSuccess is called then success is flat-mapped and failure passes through`() {
87+
// Arrange & Act
88+
val onSuccess = Outcome.Success(10).flatMapSuccess { value ->
89+
if (value > 5) Outcome.Success("success") else Outcome.Failure("failure")
90+
}
91+
val onFailure: Outcome<String, String> =
92+
Outcome.Failure("failure").flatMapSuccess { Outcome.Success("won't happen") }
93+
94+
// Assert
95+
assertThat((onSuccess as Outcome.Success).data).isEqualTo("success")
96+
assertThat((onFailure as Outcome.Failure).error).isEqualTo("failure")
97+
}
98+
99+
@Test
100+
fun `given Success and Failure when mapFailure is called then only failure is transformed and cause provided`() {
101+
// Arrange & Act
102+
val cause = RuntimeException("cause")
103+
val success = Outcome.Success("success").mapFailure { e: String, _ -> "$e?" }
104+
val failure = Outcome.Failure("fail", cause).mapFailure { e, c ->
105+
assertThat(c).isEqualTo(cause)
106+
999
107+
}
108+
109+
// Assert
110+
assertThat((success as Outcome.Success).data).isEqualTo("success")
111+
assertThat((failure as Outcome.Failure).error).isEqualTo(999)
112+
}
113+
114+
@Test
115+
fun `given Outcome when handle is invoked then calls only the matching callback`() {
116+
// Arrange
117+
var successCalledWith: Int? = null
118+
var failureCalledWith: String? = null
119+
120+
// Act
121+
Outcome.Success(5).handle(
122+
onSuccess = { successCalledWith = it },
123+
onFailure = { failureCalledWith = it },
124+
)
125+
// Assert
126+
assertThat(successCalledWith).isEqualTo(5)
127+
assertThat(failureCalledWith).isNull()
128+
129+
// Arrange
130+
successCalledWith = null
131+
val failureOutcome: Outcome<Int, String> = Outcome.Failure("failure")
132+
// Act
133+
failureOutcome.handle(
134+
onSuccess = { successCalledWith = it },
135+
onFailure = { failureCalledWith = it },
136+
)
137+
// Assert
138+
assertThat(successCalledWith).isNull()
139+
assertThat(failureCalledWith).isEqualTo("failure")
140+
}
141+
142+
@Test
143+
fun `given Outcome when handleAsync is invoked then calls only the matching suspending callback`() = runTest {
144+
// Arrange
145+
var successCalledWith: Int? = null
146+
var failureCalledWith: String? = null
147+
148+
// Act
149+
Outcome.Success(1).handleAsync(
150+
onSuccess = { successCalledWith = it },
151+
onFailure = { failureCalledWith = it },
152+
)
153+
// Assert
154+
assertThat(successCalledWith).isEqualTo(1)
155+
assertThat(failureCalledWith).isNull()
156+
157+
// Arrange
158+
successCalledWith = null
159+
val failureOutcome: Outcome<Int, String> = Outcome.Failure("failure")
160+
// Act
161+
failureOutcome.handleAsync(
162+
onSuccess = { successCalledWith = it },
163+
onFailure = { failureCalledWith = it },
164+
)
165+
// Assert
166+
assertThat(successCalledWith).isNull()
167+
assertThat(failureCalledWith).isEqualTo("failure")
168+
}
169+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package net.thunderbird.core.testing.coroutines
2+
3+
import assertk.assertThat
4+
import assertk.assertions.isFalse
5+
import assertk.assertions.isNotSameInstanceAs
6+
import assertk.assertions.isTrue
7+
import kotlin.coroutines.ContinuationInterceptor
8+
import kotlinx.coroutines.CoroutineDispatcher
9+
import kotlinx.coroutines.Dispatchers
10+
import kotlinx.coroutines.ExperimentalCoroutinesApi
11+
import kotlinx.coroutines.delay
12+
import kotlinx.coroutines.launch
13+
import kotlinx.coroutines.test.StandardTestDispatcher
14+
import kotlinx.coroutines.test.advanceUntilIdle
15+
import kotlinx.coroutines.test.runTest
16+
import kotlinx.coroutines.withContext
17+
import org.junit.Rule
18+
import org.junit.Test
19+
import org.junit.runner.Description
20+
import org.junit.runners.model.Statement
21+
22+
@OptIn(ExperimentalCoroutinesApi::class)
23+
class MainDispatcherRuleTest {
24+
25+
@get:Rule
26+
val rule = MainDispatcherRule(StandardTestDispatcher())
27+
28+
@Test
29+
fun `given rule with StandardTestDispatcher when posting to Main then it is controlled by provided scheduler`() =
30+
runTest(rule.testDispatcher) {
31+
var executed = false
32+
33+
launch(Dispatchers.Main) {
34+
delay(1000)
35+
executed = true
36+
}
37+
38+
repeat(9) {
39+
// wait a bit for the coroutine to start this delays by 900ms total
40+
if (executed) return@repeat
41+
delay(100)
42+
}
43+
44+
// Should not run until time advances on the provided scheduler
45+
assertThat(executed).isFalse()
46+
47+
rule.testDispatcher.scheduler.advanceTimeBy(1000)
48+
rule.testDispatcher.scheduler.advanceUntilIdle()
49+
50+
assertThat(executed).isTrue()
51+
}
52+
53+
@Test
54+
fun `given rule when applied around statement then sets Main and resets after evaluation`() {
55+
// Arrange
56+
val testDispatcher = StandardTestDispatcher()
57+
val localRule = MainDispatcherRule(testDispatcher)
58+
59+
val inner = object : Statement() {
60+
override fun evaluate() {
61+
runTest(testDispatcher) {
62+
withContext(Dispatchers.Main) { /* no-op */ }
63+
}
64+
}
65+
}
66+
67+
// Act
68+
val wrapped = localRule.apply(inner, Description.EMPTY)
69+
wrapped.evaluate()
70+
71+
// Assert
72+
val outcome = runCatching {
73+
var after: CoroutineDispatcher? = null
74+
runTest {
75+
withContext(Dispatchers.Main) {
76+
after = coroutineContext[ContinuationInterceptor] as CoroutineDispatcher
77+
}
78+
}
79+
after
80+
}
81+
outcome.onSuccess { after ->
82+
assertThat(after).isNotSameInstanceAs(testDispatcher)
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)