testBalloon

Project Url: infix-de/testBalloon
Introduction: TestBalloon is a coroutines-powered test framework providing structured testing for Kotlin Multiplatform. It is lightweight, heavy-lifting, and easy to use (like a balloon).
More: Author   ReportBugs   
Tags:

:icons: font

[.float-group] == image:documentation/images/Logo.svg[TestBalloon logo,120,120] TestBalloon

=== Structured testing for Kotlin Multiplatform

[quote] TestBalloon is a coroutines-powered test framework. It is lightweight, heavy-lifting, and easy to use (like a balloon).

image::documentation/images/Test%20Run.png[Example Test Run]

=== How to use TestBalloon

. Add the TestBalloon Gradle plugin to your build script: +

plugins { id("de.infix.testBalloon") version "VERSION"

}

. Add a dependency for the TestBalloon framework core library: +

[source,kotlin]

commonTest { dependencies { implementation("de.infix.testBalloon:testBalloon-framework-core:VERSION") }

}

. Add a dependency for the assertion library of your choice. ** For kotlin-test assertions: +

[source,kotlin]

implementation(kotlin("test"))

** For Kotest assertions with support for soft assertion and clues: +

[source,kotlin]

implementation("de.infix.testBalloon:testBalloon-integration-kotest-assertions:VERSION")

. Write a test: +

[source,kotlin]

val MyFirstTestSuite by testSuite { test("string length") { assertEquals(8, "Test me!".length) }

}

. Run tests via the familiar Gradle test targets.

. Coming soon: Install the TestBalloon plugin for IntelliJ IDEA from the marketplace to run individual tests or test suites via the editor's gutter icons.

=== What to expect

==== Structured testing, unified multiplatform API

The TestBalloon DSL uses two central functions, testSuite and test. Test suites can nest on all platforms, including Kotlin/JS and Kotlin/Wasm.

TestBalloon has a unified API for all targets, residing in the common source set.

[source,kotlin]

val MyTestSuite by testSuite { <1> test("string length") { <2> assertEquals(8, "Test me!".length) <3> }

testSuite("integer operations") { <4>
    test("max") {
        assertEquals(5, max(5, 3))
    }

    test("min") {
        delay(10.milliseconds) <5>
        assertEquals(3, min(5, 3))
    }
}

}

<1> Define a top-level test suite.

<2> Define a test.

<3> Use the assertion library of your choice.

<4> Nest test suites.

<5> Use coroutines everywhere, in a TestScope by default.

==== Coroutines everywhere

When tests execute, each test suite becomes a coroutine, as does each test. These coroutines nest naturally, making it easy to inherit coroutine contexts and manage resource setup and tear-down (more on this later).

==== Dynamic tests in plain Kotlin

Inside the trailing lambdas of testSuite and test, you can use all Kotlin constructs (variable scopes, conditions, loops) to create tests dynamically. There is no extra dynamic/data/factory API you need to learn. Also, no annotations, no magic.

[source,kotlin]

val Dynamic by testSuite { val testCases = mapOf( "one" to 3, "two" to 3, "three" to 5 )

testCases.forEach { (string, expectedLength) ->
    test("length of '$string'") {
        assertEquals(expectedLength, string.length)
    }
}

}

==== Test fixtures

You can use test fixtures for efficient, scoped shared state:

[source,kotlin]

val MyTestSuite by testSuite { val starRepository = testFixture { <1> StarRepository() <2> } closeWith { disconnect() <3> }

testSuite("actual users") {
    test("alina") {
        assertEquals(4, starRepository().userStars("alina")) <4>
    }

    test("peter") {
        assertEquals(3, starRepository().userStars("peter")) <5>
    }
}

} <6>

<1> Declare a test fixture at zero cost if not used.

<2> Use suspend functions in setup code.

<3> Use suspend functions in (optional) tear-down code.

<4> Use the fixture, which initializes lazily.

<5> Reuse the same fixture in other tests, sharing its setup cost.

<6> The fixture will close automatically when its suite finishes.

==== Extensible test DSL

===== Custom tests and test suites

You can use idiomatic Kotlin to define your own types of tests and test suites, like this test variant with an iterations parameter:

[source,kotlin]

fun TestSuite.test(name: String, iterations: Int, action: TestAction) { for (iteration in 1..iterations) { test("$name#$iteration") { action() } }

}

NOTE: The IDE plugin may not recognize invocations of your custom function as defining a test or test suite. In that case, xref:examples/framework-core/src/commonTest/kotlin/com/example/testLibrary/TestVariants.kt[add a @TestDiscoverable annotation to your custom test] or test suite function.

===== Wrappers

You can use TestBalloon's wrappers for setup and tear-down code. Inside the wrappers, Kotlin idioms like withTimeout and try/catch blocks can surround tests and suites.

  • xref:examples/framework-core/src/commonTest/kotlin/com/example/UsingAroundAll.kt[aroundAll] wraps a lambda around an entire test suite.
  • xref:examples/framework-core/src/commonTest/kotlin/com/example/UsingAroundEach.kt[aroundEach] wraps a lambda around each test of a test suite (including those in child suites).

==== Extensible configuration API

You can configure your tests, test suites, and global settings through a unified, small-surface API (the TestConfig builder). You can compose existing configurations as needed, and supply your own custom configurations.

[source,kotlin]

testConfig = TestConfig .invocation(TestInvocation.CONCURRENT) <1> .coroutineContext(dispatcherWithParallelism(4)) <2>

.statisticsReport() <3>

<1> Use concurrent test execution instead of the sequential default.

<2> Parallelize as needed (and the platform supports).

<3> A custom configuration for extra reporting

==== Global configuration, compartments

If you declare a subclass of TestSession, its testConfig parameter defines the global configuration for the entire compilation module. This example extends the framework's default configuration:

[source,kotlin]

class MyTestSession : TestSession(testConfig = DefaultConfiguration.statisticsReport())

To run some test suites in isolation, and/or provide them with special configuration, you can useTestCompartments. These group top-level test suites, with each compartment running in isolation.

TestSession and TestCompartments are just special types of TestSuites that form the top of the test element tree.

==== Lightweight, maintainable

TestBalloon's API is fully platform-independent (everything is in the common source set), with almost zero redundancy in its platform-specific implementations. Though powerful, TestBalloon's architecture favors simplicity and aims to avoid implicit constructs and indirection, for viable long-term maintainability.

=== Examples and documentation

Find examples demonstrating TestBalloon's capabilities in link:examples/framework-core[], and an example showing how to use TestBalloon with Kotest assertions in link:examples/integration-kotest-assertions[].

The TestBalloon public API includes source code documentation.

=== More Information

Please familiarize yourself with TestBalloon's xref:documentation/Limitations.adoc[limitations].

If you'd like to know why and how TestBalloon came to life, read about its xref:documentation/Background.adoc[background].

If you are wondering why TestBalloon works the way it does, read about its xref:documentation/Design Considerations.adoc[design considerations].

Finally, there is a brief xref:documentation/Development.adoc[introduction to development].

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools