Overview

Presentation

Kest is a test framework written in Kotlin for testing your backends.
It relies on JUnit5 to run scenarios.

Easy to use, easy to extend.

It was created to meet the need to test backends and to interact with the middlewares on which they are based in a very simple way, in order to be able to concentrate on the functional to be tested by avoiding difficulty or heaviness brought about by interactions with multiple components.

Use case :

My backend consumes messages in a RabbitMQ queue, it exposes its data over HTTP, HTTP access is protected. To test the behavior of my backend I will have to:

  1. post messages in RabbitMQ

  2. provision users in my mongo database

  3. make HTTP requests to my backend

⇒ I’m going to need to interact with three different technologies, with different clients to handle.
⇒ Kest allows you to get away from it all
⇒ Kest is an engine that will play scenarios, scenarios will be a sequence of steps
⇒ Kest provides ready-made steps to interact with HTTP, RabbitMQ, MongoDB, Redis, and Cadence
⇒ Kest allows you to define your own steps: you use a techno not supported by Kest? design your steps!

Use it

Gradle

    implementation("com.github.lemfi.kest:core:0.8.0")
    implementation("com.github.lemfi.kest:junit5:0.8.0")

    // include the steps you need among available ones
    implementation("com.github.lemfi.kest:step-http:0.8.0")
    implementation("com.github.lemfi.kest:step-cadence:0.8.0")
    implementation("com.github.lemfi.kest:step-mongodb:0.8.0")
    implementation("com.github.lemfi.kest:step-rabbitmq:0.8.0")
    implementation("com.github.lemfi.kest:step-redis:0.8.0")

    // toolbox for Json Data
    implementation("com.github.lemfi.kest:json:0.8.0")

Maven

<dependencies>
    <dependency>
        <groupId>com.github.lemfi.kest</groupId>
        <artifactId>core</artifactId>
        <version>0.8.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.github.lemfi.kest</groupId>
        <artifactId>junit5</artifactId>
        <version>0.8.0</version>
        <scope>test</scope>
    </dependency>

    <!-- include the steps you need among available ones -->
    <dependency>
        <groupId>com.github.lemfi.kest</groupId>
        <artifactId>step-http</artifactId>
        <version>0.8.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.github.lemfi.kest</groupId>
        <artifactId>step-rabbitmq</artifactId>
        <version>0.8.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.github.lemfi.kest</groupId>
        <artifactId>step-cadence</artifactId>
        <version>0.8.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.github.lemfi.kest</groupId>
        <artifactId>step-mongodb</artifactId>
        <version>0.8.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.github.lemfi.kest</groupId>
        <artifactId>step-redis</artifactId>
        <version>0.8.0</version>
        <scope>test</scope>
    </dependency>

    <!-- toolbox for Json Data -->
    <dependency>
        <groupId>com.github.lemfi.kest</groupId>
        <artifactId>step-json</artifactId>
        <version>0.8.0</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Big picture

Scenarios

Kest allows you to write scenarios.
One scenario is a sequence of steps.
One step defines an execution and a list of assertions to validate completed execution.

Let’s take following example:

scenario(name = "api says hello and remembers it!") {

    givenHttpCall<String> {

        url = "http://myapi/hello"
        method = "POST"
        headers["Authorization"] = "Basic aGVsbG86d29ybGQ="
        body = """
            {
                "who": "Darth Vader"
            }
        """
    } assertThat { stepResult ->

        stepResult.status isEqualTo 201
        stepResult.body  isEqualTo "Hello Darth Vader!"
    }

    givenHttpCall<String> {

        url = "http://myapi/hello"
        method = "POST"
        headers["Authorization"] = "Basic aGVsbG86d29ybGQ="
        body = """
            {
                "who": "Han Solo"
            }
        """
    } assertThat { stepResult ->

        stepResult.status isEqualTo 201
        stepResult.body isEqualTo "Hello Han Solo!"
    }

    givenHttpCall<List<String>> {

        url = "http://myapi/hello"
        method = "GET"
        headers["Authorization"] = "Basic aGVsbG86d29ybGQ="

    } assertThat { stepResult ->

        stepResult.status isEqualTo 200
        stepResult.body  isEqualTo listOf("Darth Vader", "Han Solo")
    }
}

This example defines a scenario with three steps:

  1. Say hello to Darth Vader

  2. Say hello to Han Solo

  3. Get list of people that were greeted

For each step 2 assertions are done:

  1. check status code

  2. check body

Run a scenario

To run a scenario Kest uses Junit5 engine:

  1. Create a class to hold your test

  2. Create a function annotated with Junit5 @TestFactory

  3. Make it call Kest function

    1. playScenarios to play multiple scenarios in a single junit5 test factory
      ⇒ in this case you can pass beforeEach and afterEach attributes representing a lambda returning a scenario to be played before and after each scenario if needed. ⇒ in this case you can pass beforeEach and afterEach attributes representing a lambda returning a scenario to be played before and after each scenario if needed.

    2. playScenario to play one scenario per junit5 test factory
      ⇒ in this case you can use junit5 annotations @BeforeEach and @AfterEach

class TestHttpServer {

    @TestFactory
    fun `http server says hello`() = playScenarios(
            scenario { /*...*/ },
            scenario { /*...*/ },
            scenario { /*...*/ },

            beforeEach = { scenario { /*...*/ }},
            afterEach = { scenario { /*...*/ }}
    )


    @TestFactory
    fun `http server says goodbye`() = playScenarios(
            scenario { /*...*/ },
            scenario { /*...*/ },
            scenario { /*...*/ },

            beforeEach = { scenario { /*...*/ }},
            afterEach = { scenario { /*...*/ }}
    )

}

class TestHttpServer {

    @BeforeEach
    fun beforeEach() {
        /*...*/
    }

    @AfterEach
    fun afterEach() {
        /*...*/
    }

    @TestFactory
    fun `http server says hello - scenario 1`() = playScenario(
            scenario { /*...*/ }
    )

    @TestFactory
    fun `http server says hello - scenario 2`() = playScenario(
            scenario { /*...*/ }
    )

}

Focus on how it works

model

When a scenario is launched, all its steps are launched sequentially, if one step fails the scenario fails without running remaining steps.
When a step is launched its execution is played, then assertions are played against execution result.
A step returns an object . which is invokable: you may reuse its result in another step . which you may complete by your own assertions, they will be added to assertions already defined on step