Skip to content

Commit 589a79f

Browse files
committed
0.1.1
1 parent 5a3bddc commit 589a79f

20 files changed

Lines changed: 739 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Changelog
2+
3+
## 0.x
4+
5+
### 0.1.x
6+
7+
#### 0.1.1
8+
* Per-test fixture generation addon
9+
* Update to TestBalloon 0.7.1
10+
11+
#### 0.1.0
12+
Initial release

README.md

Lines changed: 178 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,33 @@ At the same time, **the Kotest _libraries_, like its assertions, the way it mode
2222
unrivaled** and don't suffer from the framework's shortcomings. Paired with TestBalloon's flexibility and its small API
2323
surface, we can get the best of both worlds.
2424

25+
26+
27+
> [!IMPORTANT]
28+
> Always explicitly add `de.infix.testBalloon:testBalloon-framework-core` **≥ 0.7.0** to your test dependencies!
29+
> You will run into an unresolved dependency error otherwise!
30+
2531
## Modules
2632

27-
This project consists of three modules:
33+
This project consists of the following modules:
2834

2935
* `freespec` emulating Kotest's `FreeSpec` test style for TestBalloon
3036
* `datatest` replicates Kotest's data-driven testing features for TestBalloon
3137
* `property` bringing Kotest's property testing to TestBalloon
38+
* `fixturegen` introducing per-test fixture generation for TestBalloon without boilerplate
39+
40+
> [!TIP]
41+
> `freespec` and `fixturegen` are [modulated](https://github.com/a-sit-plus/modulator) into the `fixturegen-freespec` module, meaning that if you add the
42+
> `at.asitplus.modulator` gradle plugin to any project that uses both, you can automagically combine FreeSpec syntax
43+
> and per-test fixture generation! If you don't want to use modulator, you can just add the `at.asitplus.testballoon:fixturegen-freespec:$version`
44+
> dependency manually to your project.
3245
33-
> [!IMPORTANT]
34-
> Always explicitly add `de.infix.testBalloon:testBalloon-framework-core` **≥ 0.7.0** to your test dependencies!
35-
> You will run into an unresolved dependency error otherwise!
3646

3747
### FreeSpec
3848

3949
| Maven Coordinates | `at.asitplus.testballoon:freespec:$version` |
4050
|-------------------|---------------------------------------------|
4151

42-
4352
At A-SIT Plus, we've been using Kotest's [FreeSpec](https://kotest.io/docs/framework/testing-styles.html#free-spec) for
4453
its expressiveness, as it allows modeling tests and test dependencies close to natural language.
4554

@@ -57,6 +66,7 @@ kotlin {
5766
}
5867
}
5968
```
69+
6070
</details>
6171

6272
```kotlin
@@ -94,7 +104,6 @@ val aFreeSpecSuite by testSuite {
94104
| Maven Coordinates | `at.asitplus.testballoon:datatest:$version` |
95105
|-------------------|---------------------------------------------|
96106

97-
98107
TestBalloon makes it ridiculously easy to roll your own data-driven testing wrapper with just a couple of lines of code.
99108
So we did, by replicating Kotest's data-driven testing API:
100109

@@ -117,7 +126,6 @@ val aDataDrivenSuite by testSuite {
117126
| Maven Coordinates | `at.asitplus.testballoon:property:$version` |
118127
|-------------------|---------------------------------------------|
119128

120-
121129
Although it comes with some warts, `kotest-property` is still extremely helpful to generate a large corpus of test data,
122130
especially as it covers many edge cases out of the box. Again, since TestBalloon has been specifically crafted to be
123131
flexible and extensible, we did just that:
@@ -141,6 +149,169 @@ val propertySuite by testSuite {
141149
}
142150
```
143151

152+
### Per-Test Fixture Generation
153+
154+
| Maven Coordinates | `at.asitplus.testballoon:fixturegen:$version` |
155+
|-------------------|-----------------------------------------------|
156+
157+
TestBalloon enforces a strict separation between blue code and green code. This is a good thing, especially for deeply
158+
nested
159+
test suites, and it supports deep concurrency,
160+
Hence, ye olde JUnit4-style `@Before` and `@After` hacks mutating global state are deliberately not supported.
161+
Sometimes, though, you really want fresh test data for every test&mdash;in effect, **you want to generate a fresh test
162+
fixture
163+
for every test**.
164+
165+
Look no further , **if** you have [context parameters](https://kotlinlang.org/docs/context-parameters.html) enabled for
166+
your codebase:
167+
168+
<details>
169+
<summary>Setting up context parameters</summary>
170+
171+
```kotlin
172+
// build.gradle.kts
173+
kotlin {
174+
compilerOptions {
175+
freeCompilerArgs.add("-Xcontext-parameters")
176+
}
177+
}
178+
```
179+
180+
</details>
181+
182+
```kotlin
183+
import at.asitplus.testballoon.generatingFixtureFor //<- Look ma, only a single import!
184+
import de.infix.testBalloon.framework.core.testSuite
185+
import kotlin.random.Random
186+
187+
val aGeneratingSuite by testSuite {
188+
189+
//seed the RNG for reproducible tests
190+
val random = Random(42)
191+
192+
//reference function to be called for each test inside generatingFixtureFor
193+
random::nextFloat.generatingFixtureFor {
194+
repeat(10) {
195+
test("Generated test with random float") {
196+
//test something floaty!
197+
}
198+
}
199+
}
200+
201+
202+
//seed before the generator function, not inside!
203+
val byteRNG = Random(42);
204+
//We want to test with fresh randomness, so we generate a fresh fixture for each test
205+
{ byteRNG.nextBytes(32) }.generatingFixtureFor {
206+
207+
repeat(5) {
208+
test("Generated test with fresh randomness") { freshFixture ->
209+
//your test logic here
210+
}
211+
}
212+
213+
repeat(5) {
214+
//✨ it ✨ just ✨ werks ✨
215+
test("Test with implicit fixture name `it`") {
216+
//do something with `it`, it contains fresh randomness!
217+
}
218+
}
219+
}; //<- semicolon needed, because what follows is a bare lambda
220+
221+
222+
//always-the-same fixtures also work, of course
223+
{
224+
object {
225+
var a: Int = 1
226+
val b: Int = 2
227+
}
228+
}.generatingFixtureFor {
229+
test("one") {
230+
it.a++ //and we can even modify them in one test
231+
println("a=${it.a}, b=${it.b}") //a=2, b=2
232+
}
233+
test("two") {
234+
//without affecting the other!
235+
println("a=${it.a}, b=${it.b}") //a=1, b=2
236+
}
237+
}
238+
239+
240+
//Let's test some nasty bug that shows itself only sometimes functionality
241+
val ageRNG = Random(seed = 26); //<- semicolon needed, because what follows is a bare lambda
242+
{
243+
class ABuggyImplementation(val age: Int) {
244+
fun restrictedAction(): Boolean =
245+
if (age < 18) false
246+
else if (age > 18) true
247+
else Random.nextBoolean() //introduce jitter to simulate a faulty implementation
248+
}
249+
250+
//create new object for each test
251+
ABuggyImplementation(ageRNG.nextInt(0, 99))
252+
}.generatingFixtureFor {
253+
repeat(1000) {
254+
test("Generated test accessing restricted resources") {
255+
//test `restrictedAction` across a wide age range
256+
//a thousand times to unveil the bug
257+
}
258+
}
259+
}
260+
}
261+
262+
```
263+
264+
<details>
265+
<summary>Combining with FreeSpec</summary>
266+
267+
268+
| Maven Coordinates (if not using [modulator](https://github.com/a-sit-plus/modulator)) | `at.asitplus.testballoon:fixturegen-freespec:$version` |
269+
|---------------------------------------------------------------------------------------|--------------------------------------------------------|
270+
271+
272+
273+
```kotlin
274+
import at.asitplus.testballoon.generatingFixtureFor //<- Look ma, only regular generatingFixtureFor import!
275+
import at.asitplus.testballoon.invoke // <- Look ma, only regular freespec import!
276+
import at.asitplus.testballoon.minus // <- Look ma, only regular freespec import!
277+
import de.infix.testBalloon.framework.core.testSuite
278+
import kotlin.random.Random
279+
280+
val aGeneratingFreeSpecSuite by testSuite {
281+
282+
//any lambda with any return type is a fixture generator. Type is reified.
283+
{ Random.nextBytes(32) }.generatingFixtureFor {
284+
285+
"A Test with fresh randomness" { freshFixture ->
286+
//your test logic here
287+
}
288+
289+
repeat(100) {
290+
"Generated test with fresh randomness" { freshFixture ->
291+
//some more test logic; each call gets fresh randomness
292+
}
293+
}
294+
295+
//✨it ✨just ✨werks ✨
296+
"Test with implicit fixture name `it`" {
297+
//no need for an explicit parameter name here, just use `it`
298+
}
299+
300+
"And we can even nest!" - {
301+
{ Random.nextBytes(16) }.generatingFixtureFor {
302+
repeat(10) {
303+
"pure, high-octane magic going on" {
304+
//Woohoo! more randomness each run
305+
}
306+
}
307+
}
308+
}
309+
}
310+
}
311+
```
312+
313+
</details>
314+
144315
## Contributing
145316

146317
External contributions are greatly appreciated!

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import org.jetbrains.dokka.gradle.DokkaMultiModuleTask
22
import java.time.Duration
33

44
System.setProperty("KOTEST_NO_ASP_HELPER", "true")
5+
System.setProperty("TESTBALLOON_NO_ASP_HELPER","true")
56
plugins {
67
alias(libs.plugins.asp)
78
alias(libs.plugins.agp) apply false

datatest/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import at.asitplus.gradle.setupDokka
55
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
66

77
System.setProperty("KOTEST_NO_ASP_HELPER", "true")
8+
System.setProperty("TESTBALLOON_NO_ASP_HELPER","true")
89

910
plugins {
1011
alias(libs.plugins.kmp)

datatest/src/commonMain/kotlin/at/asitplus/test/TestBalloonDataTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package at.asitplus.testballoon
22

33
import de.infix.testBalloon.framework.core.TestConfig
44
import de.infix.testBalloon.framework.core.TestSuite
5+
import de.infix.testBalloon.framework.shared.TestRegistering
56

67
/**
78
* Executes a test for each provided data parameter.

fixturegen-freespec/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
generated/

0 commit comments

Comments
 (0)