3

I have this test that launches a service at port 7000 and inside the only endpoint I do a failing assertion:

@Test
fun `javalin assertion should fail`() {
    Javalin.create()
            .get("/") { assertTrue(false) }
            .start()

    newHttpClient().send(
            HttpRequest.newBuilder()
                    .uri(URI.create("http://localhost:7000/"))
                    .GET().build(),
            discarding()
    )
}

The problem is that the test always passes (but it should fail):

enter image description here

(same behavior happens by running ./gradlew test)

... even though there's a console output claiming that a test failed:

[Test worker] INFO io.javalin.Javalin - Listening on http://localhost:7000/
[Test worker] INFO io.javalin.Javalin - Javalin started in 356ms \o/
[qtp2100106358-22] ERROR io.javalin.Javalin - Exception occurred while servicing http-request
org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
    at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)
    ..

Probably, it's being run in another thread, but I wonder if there's a way to attach it to the same context. (Weirdly, in another scenario in my app - that I couldn't isolate - it properly fails.)

Luís Soares
  • 5,726
  • 4
  • 39
  • 66

2 Answers2

3

TLDR

To make the test fail as you'd expect, add an assertion on the response you get from your Javalin instance.

    @Test
    fun `javalin assertion should fail`() {
        Javalin.create()
            .get("/") { assertTrue(false) } // or any expression that throws an Exception, like Kotlin's TODO()
            .start()

        val javalinResponse: HttpResponse<Void> = newHttpClient().send(
            HttpRequest.newBuilder()
                .uri(URI.create("http://localhost:7000/"))
                .GET().build(),
            discarding()
        )

        assertThat(javalinResponse.statusCode()).isEqualTo(200) // will fail with Expected: 200, Actual: 500
    }

Details

There're two distinct steps in the test: new Javalin instance configuration + build, and calling that instance with a HttpClient.

In Javalin configuration + build step it gets instructed to do assertTrue(false) when called on the / endpoint. assertTrue(false) will throw an AssertionFailedError, but the behavior will be the same if you throw something else there. Now, as many (all?) other webservers, Javalin / Jetty will try to catch any uncaught exceptions that happen within it and return a HTTP response with code 500 (Internal Server Error).

Indeed, this all happens in another thread, as internally a Jetty webserver instance is being launched, which does the port listening, HTTP request / response handling and other important stuff.

So when later in the test an HTTP call is performed to the new Javalin instance, it gets the 500 (Internal Server Error) response successfully, and as originally there are no assertions on the response and there were no uncaught exceptions, the test is deemed successful.

sa1nt
  • 356
  • 1
  • 4
  • Hey! Thanks for the reply. However, it's not enough for my needs. Please read my comments explaining why here: https://github.com/tipsy/javalin/issues/813#issuecomment-556504232 and here: https://github.com/tipsy/javalin/issues/813#issuecomment-557511615 – Luís Soares Nov 26 '19 at 08:11
1

Never do assertions inside the Javalin handler, because if the test fails, the JUnit exception is swallowed by Javalin and the test fails silently (better explained in the other answer). The solution is to make an assertion outside, in the end, as in the Arrange, Act, Assert pattern.

How? You store what you want to assert inside the handler and assert it later. For example, if it's a POST.

var postedBody: String? = null
fakeProfileApi = Javalin.create().post("profile") {
    postedBody = it.body()
}.start(1234)
val profileGateway = ProfileGateway(apiUrl = "http://localhost:1234")

profileGateway.saveProfile(  // contains the HTTP POST
   Profile(id = "abc", email = "john.doe@gmail.com".toEmail())
)

JSONAssert.assertEquals(
    """ { "id": "abc", "email": "johndoe@gmail.com" } """,
    postedBody, true
)

If it's a GET, it's easier:

fakeProfileApi = Javalin.create().get("profile/abc") {
   it.result(""" {"id": "abc", "email": "johndoe@gmail.com"} """)
}.start(1234)
val profileGateway = ProfileGateway(apiUrl = "http://localhost:1234")

val result = profileGateway.fetchProfile("abc") // contains the HTTP GET

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

More info: Unit testing a gateway with Javalin

Luís Soares
  • 5,726
  • 4
  • 39
  • 66