How to write beautiful unit tests in Vert.x
Tags: sip3, kotlin, vertx, testing
Published: 2020-04-24 15:08:38

As you already know from my [previous](https://dev.to/sip3/scaling-vert-x-application-for-session-dependent-data-processing-16pd) blog post the [SIP3](https://sip3.io) team is in love with the incredible [Vert.x](https://vertx.io/) framework.

Reactive nature makes Vert.x extremely fast and scalable, but (as everything else) it all comes with a price :imp:... And here I'm talking about writing unit tests (:smiling_imp::smiling_imp::smiling_imp: of course if you do write them :smiling_imp::smiling_imp::smiling_imp:)...

Is it really that complicated to write tests with Vert.x?

Let's assume that we want to check a simple thing: that a message sent via event bus was received by its consumer. 

In a perfect world we would expect to see something like this (please pay attention to the test structure):

```kotlin
class MyVertxTest {

    @Test
    fun `Send message to the address`() {
        // 1. Define our message
        val message = "Hello, world!"

        val vertx = Vertx.vertx()

        // 2. Send it to the address
        vertx.eventBus().send("address", message)
        // 3. Retrieve and check the message
        vertx.eventBus().consumer<String>("address") { event ->
            assertEquals(message, event.body())
        }
    }
}
```

Obviously this code won't be working because:
1. There is no consumer at the moment we've sent the message;
2. Even if there was a consumer `assertEquals()` will be executed not in the context of our test thread.

Of course, the problem is not new and Vert.x team has already resolved it by introducing a `TestContext` object (learn more about that from official documentation: [here](https://vertx.io/docs/vertx-unit/kotlin/) and [here](https://vertx.io/docs/vertx-junit5/java/)).

With the `TestContext` and JUnit5 our code will look like this:

```kotlin
@ExtendWith(VertxExtension::class)
class MyVertxTest {

    @Test
    fun `Send message to the address`() {
        // 1. Define our message
        val message = "Hello, world!"

        val context = VertxTestContext()
        val vertx = Vertx.vertx()

        // 3. Retrieve and check the message
        vertx.eventBus().consumer<String>("address") { event ->
            context.verify {
                assertEquals(message, event.body())
            }
            context.completeNow()
        }

        // 2. Send it to the address
        vertx.eventBus().send("address", message)

        assertTrue(context.awaitCompletion(5, TimeUnit.SECONDS))
        if (context.failed()) {
            throw context.causeOfFailure();
        }
    }
}
```

Not like in our previous example, this code will be working fine, but:
1. Assertion on the `context` object brings too much overhead;
2. We would love to keep the order - define test context, execute scenario and only after make all the assertions.

Trying to satisfy the last two requirements the SIP3 team decided to introduce its own test class wrapper:

```kotlin
@ExtendWith(VertxExtension::class)
open class VertxTest {

    lateinit var context: VertxTestContext
    lateinit var vertx: Vertx

    fun runTest(deploy: (suspend () -> Unit)? = null, execute: (suspend () -> Unit)? = null,
                assert: (suspend () -> Unit)? = null, cleanup: (() -> Unit)? = null, timeout: Long = 10) {
        context = VertxTestContext()
        vertx = Vertx.vertx()
        GlobalScope.launch(vertx.dispatcher()) {
            assert?.invoke()
            deploy?.invoke()
            execute?.invoke()
        }
        assertTrue(context.awaitCompletion(timeout, TimeUnit.SECONDS))
        cleanup?.invoke()
        if (context.failed()) {
            throw context.causeOfFailure()
        }
    }
}
```

We put a `context` object assertion within the `runTest()` method. Also we used Kotlin named arguments(which is a really cool) to define test stages.

Let's rewrite our test class using `VertxTest`:

```kotlin
class MyVertxTest : VertxTest() {

    @Test
    fun `Send message to the address`() {
        // 1. Define our message
        val message = "Hello, world!"

        runTest(
                execute = {
                    // 2. Send it to the address
                    vertx.eventBus().send("address", message)
                },
                assert = {
                    // 3. Retrieve and check the message
                    vertx.eventBus().consumer<String>("address") { event ->
                        context.verify {
                            assertEquals(message, event.body())
                        }
                        context.completeNow()
                    }
                }
        )
    }
}
```

Of course, it's a matter of taste but we think that the code above is clean and simple even though there is always room for improvement :smile:.

The SIP3 team uses this approach in all it projects. Feel free to [check out our github](https://github.com/sip3io) and let us know if you loved it.

Cheers...