Testing a gateway client using Javalin

In Kotlin/Java, how would you test that your app properly consumes an external REST API? We’ll use Javalin to do it.

Luís Soares
CodeX
4 min readJun 5, 2020

--

How do you unit test a gateway in your app?

📝 I adapted this article to the official Javalin docs page.

I propose to use Javalin (a lightweight webserver) as the test double, thereby replacing the external API (the DOC: dependent-on component). We’ll launch Javalin acting as the real API but running in localhost, so the gateway (the SUT) can’t tell the difference. We’ll confirm the validity by asserting the calls made to the test double.

⚠️ Don’t confuse testing API calls with testing your API handlers. We want to tackle the former: testing a REST client, not a REST service.

Before starting

If you don’t have a project, create one and add Javalin.

dependencies {
testImplementation("io.javalin:javalin:5.6.3")
testImplementation("org.slf4j:slf4j-simple:2.0.7")
}

We’ll have two examples of testing ProfileGateway: a query and a command, according to the command/query 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.

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

Example #1: testing a GET to an API

Imagine 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.

@Test
fun `gets a user profile by id`() {
Javalin.create().get("profile/abc") {
it.json(mapOf("id" to "abc", "email" to "x123@gmail.com"))
}.start().use {
val gatewayClient = ProfileGateway(apiUrl = "http://localhost:${it.port()}")

val result = gatewayClient.fetchProfile("abc")

assertEquals(Profile(id = "abc", email = "x123@gmail.com".toEmail()), result)
}
}

General recipe

  1. 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;
  2. act: call the subject method that fetches the data;
  3. 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.

@Test
fun `posts a user profile`() {
var postedBody: String? = null
Javalin.create().start().post("profile") {
postedBody = it.body()
it.status(201)
}.use {
val gatewayClient = ProfileGateway(apiUrl = "http://localhost:${it.port()}")

gatewayClient.saveProfile(Profile(id = "abc", email = "x123@gmail.com".toEmail()))

assertEquals(
ObjectMapper().valueToTree(mapOf("id" to "abc", "email" to "x123@gmail.com")),
ObjectMapper().readTree(postedBody)
)
}
}

General recipe

  1. Arrange: prepare Javalin with a single handler to simulate your external API endpoint; inside the handler, store what you want to assert later, like path, headers, and body. Pass that server URL to the component under test as its base URL.
  2. Act: call the subject method that executes the side effect;
  3. 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).

⚠️ Never make assertions inside the Javalin test handler. Because if they fail, they’ll throw a JUnit exception, which Javalin handles as a 500; the test will be green! Always do the assertions in the end following the Arrange, Act, Assert pattern.

Alternative approaches

It’s important to mention alternatives to the Javalin proposal:

  • 🛑 Mocking the HTTP client (e.g., with MockK, HttpClientMock, vrc, ExVCR, VCR.py)
    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 — 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, WireMock, JSON Server)
    It’s 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. In addition, when a test fails, Javalin makes it easier to fix it.
  • End-to-end testing: This is complementary to unit testing rather than replacing it.

📝 If you’re using Javalin as your app web server, it’s even better, as you won’t add extra libraries for testing.

The examples are in a GitHub repo (test and implementation).

--

--

Luís Soares
CodeX

I write about automated testing, Lean, TDD, CI/CD, trunk-based dev., user-centric dev, domain-centric arch, ...