Skip to content

Commit

Permalink
add tutorial about sing Javalin as a simulator for HTTP-based APIs (j…
Browse files Browse the repository at this point in the history
…avalin#103)

* add tutorial about sing Javalin as a simulator for HTTP-based APIs

* add generic utility method

* add reference to JSONassert
  • Loading branch information
lsoares authored Jul 13, 2021
1 parent c031db1 commit a11d9fc
Showing 1 changed file with 216 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
---
layout: tutorial

official: false

title: Using Javalin as a simulator for HTTP-based APIs

author: <a href="https://twitter.com/lsoares" target="_blank">Luís Soares</a>

date: 2021-07-11

permalink: /tutorials/using-javalin-as-http-simulator

github: https://github.com/lsoares/clean-architecture-sample

summarytitle: Javalin as a simulator for HTTP-based APIs

summary: Let's learn how you can test that your app is properly consuming an external REST API making use of Javalin as
a simulator of HTTP APIs that your app depends upon.

language: kotlin

---

_(adapted
from [Testing a gateway using Javalin](https://medium.com/@lsoares/unit-testing-a-gateway-with-javalin-24e3b7e88ef2))_

My proposal is to use Javalin as the test double - fake gateway, thereby replacing some depended-on external API. We’ll
launch Javalin acting as the real API but running in *localhost* so that the gateway client
(the test subject) can’t tell the difference. We’ll confirm the tests validity by asserting the calls made to the test
double.

Let's imagine a client that talks to some external HTTP API service - in our case some "User Profile API". We’ll have
two examples of (unit) testing `ProfileGateway`: a query and a command, according to the
[command/query](https://martinfowler.com/bliki/CommandQuerySeparation.html) separation:

- **query**: check that it properly consumes a GET response from an external party; we’ll assert the output of a method;

- **command**: check that a POST call was made as expected; we’ll assert a consequence, namely the posted body.

Before starting, make sure you have Javalin in your Gradle file. If you’re using Javalin as your app web server, you
won’t add extra libraries for testing. Otherwise, you need to include it only for the tests. Also, let's
add [JSONassert](https://github.com/skyscreamer/JSONassert) library to make JSON comparison easier, although that's not
mandatory as we could compare it in other ways.

```kotlin
testImplementation("io.javalin:javalin:3.+")
testImplementation("org.skyscreamer:jsonassert:1.+")
```

Let's start with a boilerplate that guarantees that Javalin stops per every test (or tests will start to influence each
other due to used ports):

```kotlin
class ProfileGatewayTest {

private lateinit var fakeProfileApi: Javalin

@AfterEach
fun `after each`() {
fakeProfileApi.stop()
}
}
```

Don’t worry as this won’t make your tests slow; Javalin is extremely fast booting up (hundreds of start/stops in a few \
seconds).

I won't include the implementations because this tutorial is focused on testing. If you're curious, you can find them at
a [sample project](https://github.com/lsoares/clean-architecture-sample) I have for experiments (search
for `profilegateway`).

## Example #1: testing a GET to an API

Let’s say your app depends on an external API to fetch the user’s profile — a query. We need to test
that `ProfileGateway` handles it well, namely the parsing and proper transformation of data.

```kotlin
@Test
fun `gets a user profile by id`() {
fakeProfile = Javalin.create().get("profile/abc") {
it.json(mapOf("id" to "abc", "email" to "[email protected]"))
}.start(1234)
val profileGateway = ProfileGateway(apiUrl = "http://localhost:1234")

val result = profileGateway.fetchProfile("abc")

assertEquals(Profile(id = "abc", email = "[email protected]".toEmail()), result)
}
```

### General recipe

Notice that we have [three parts](http://wiki.c2.com/?ArrangeActAssert) in the code above:

- **arrange**: prepare Javalin with a single handler only to simulate your external API endpoint; inside the handler,
write a stubbed response as if you were the API owner;
- **act**: call the subject method that fetches the data;
- **assert**: test that your subject correctly parsed the stubbed response (API JSON → your domain representation);
optionally, you can check the number of calls and HTTP details (e.g. if you sent the proper headers).

## Example #2: testing a POST to an API

Now we’ll see an example of a command — there’s a side-effect to be tested. In this case, we need to assert that the
data was properly prepared and posted to the third party by the `ProfileGateway`. The HTTP call details can be tested as
well.

```kotlin
@Test
fun `posts a user profile`() {
var postedBody: String? = null
var contentType: String? = null
fakeProfileApi = Javalin.create().post("profile") {
postedBody = it.body()
contentType = it.contentType()
it.status(201)
}.start(1234)
val profileGateway = ProfileGateway(apiUrl = "http://localhost:1234")

profileGateway.saveProfile(
Profile(id = "abc", email = "[email protected]".toEmail())
)

JSONAssert.assertEquals(
""" { "id": "abc", "email": "[email protected]" } """,
postedBody, true
)
assertEquals("application/json", contentType)
}
```

### General recipe

- **arrange**: prepare Javalin with a single handler only to simulate your external API endpoint; inside the handler,
store what you want to assert later, like path, headers, and body;
- **act**: call the subject method that executes the side-effect;
- **assert**: test that the stored values in the handler are correct; for example, the body must have been properly
converted to the external API (your domain representation → API JSON).

⚠️ Whatever you do, never do assertions inside the Javalin test handler. Why? Because if they fail, they’ll throw a
JUnit exception, which is swallowed by Javalin; and the test will be green! Always do the assertions in the end hence
following the Arrange, Act, Assert pattern.

## Making it generic

Notice that we have the server as a global variable (bad practice), we have to start it in every test, and stop it after
each. If you think you'll do more that just a few test variations, it's worth getting rid of that boilerplate code.
Let's create a reusable utility that starts the fake gateway and initializes our test subject:

```kotlin
fun testProfileGateway(testBody: (Javalin, ProfileGateway) -> Unit) {
val server = Javalin.create().start(0)
val gatewayClient = ProfileGateway(apiUrl = "http://localhost:${server.port()}")
testBody(server, gatewayClient)
server.stop()
}
```

Let's make use of it:

```kotlin
@Test
fun `gets a user profile by id`() = testProfileGateway { server, gatewayClient ->
server.get("profile/abc") {
it.json(mapOf("id" to "abc", "email" to "[email protected]"))
}

val result = gatewayClient.fetchProfile("abc")

assertEquals(Profile(id = "abc", email = "[email protected]".toEmail()), result)
}

@Test
fun `posts a user profile`() = testProfileGateway { server, profileGateway ->
var postedBody: String? = null
var contentType: String? = null
server.post("profile") {
postedBody = it.body()
contentType = it.contentType()
it.status(201)
}

profileGateway.saveProfile(Profile(id = "abc", email = "[email protected]".toEmail()))

JSONAssert.assertEquals(
""" { "id": "abc", "email": "[email protected]"} """,
postedBody, true
)
assertEquals("application/json", contentType)
}
```

Another benefit of this approach is that we hide some low-level details like startup of the fake server and its base URL
and port. We focus our test on what really matters.

ℹ️ This approach in inspired in `javalin-testtools` which will be available in Javalin 4.

## Alternative approaches

It’s important to mention alternatives to the Javalin proposal. The decision depends on your testing strategy.

- 🛑 **Mocking the HTTP client** (e.g., with Mockito, MockK, HttpClientMock)
You’d be mocking what you don’t own; would you mock a database driver? Mocking a REST client would be as bad. You’d
couple the test with the HTTP client, which is an implementation detail. With Javalin, it doesn’t matter which REST
client you use in your implementation (Java HTTP client, Apache HTTP Client, Retrofit, etc).
- **Mocking a wrapper around the HTTP client**
You’re creating just an additional pass-through layer. Also, you’re not emulating HTTP anyway. Finally, you’re not
abstracting the external party data model. Using Javalin, you have the (localhost) network involved, so it’s much more
realistic. For example, it’s trivial to simulate network errors (e.g., 401 Unauthorized) to see how your system
handles them.
- **Using a simulator for HTTP-based APIs** (e.g., MockServer or WireMock)
The same technique as the Javalin proposal but with a dedicated library with a built-in DSL. I tried both and ended up
replacing them with Javalin because I didn’t want to learn their DSL. I was already using Javalin in my app anyway. In
addition, when a test fails, Javalin makes it easier to fix it.
- **Integration, system, contract, and end-to-end testing**. These are complementary to unit testing and they do not
replace it.

0 comments on commit a11d9fc

Please sign in to comment.