=== Testing

Jooby provides dedicated tools for both lightweight unit testing and full-blown integration testing.

==== Unit Testing

Unit testing in Jooby is fast because it allows you to test your routes without starting a real HTTP server.

1) Add the Dependency:

[dependency, artifactId="jooby-test"]
.

2) Define your Application:

.App
[source,java,role="primary"]
----
public class App extends Jooby {
  {
    get("/", ctx -> "Easy unit testing!");
  }
}
----

.Kotlin
[source,kotlin,role="secondary"]
----
class App : Kooby({
  get("/") { "Easy unit testing!" }
})
----

3) Write the Test:

Use the javadoc:test.MockRouter[artifact=jooby-test] to simulate requests and capture the return values of your handlers.

.TestApp
[source,java,role="primary"]
----
import io.jooby.test.MockRouter;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class TestApp {
  @Test
  public void test() {
    MockRouter router = new MockRouter(new App());
    assertEquals("Easy unit testing!", router.get("/").value());
  }
}
----

.Kotlin
[source,kotlin,role="secondary"]
----
import io.jooby.test.MockRouter
import org.junit.jupiter.api.Assertions.assertEquals

class TestApp {
  @Test
  fun test() {
    val router = MockRouter(App())
    assertEquals("Easy unit testing!", router.get("/").value())
  }
}
----

===== Checking Response Metadata

If your route modifies the context (like setting status codes or headers), you can verify the metadata using a callback:

.Metadata Test
[source,java,role="primary"]
----
@Test
public void testMetadata() {
  MockRouter router = new MockRouter(new App());
  router.get("/", response -> {
    assertEquals(StatusCode.OK, response.getStatusCode());
    assertEquals("Easy unit testing", response.value(String.class));
  });
}
----

.Kotlin
[source,kotlin,role="secondary"]
----
@Test
fun testMetadata() {
  val router = MockRouter(App())
  router.get("/") { response ->
    assertEquals(StatusCode.OK, response.statusCode)
    assertEquals("Easy unit testing", response.value(String::class.java))
  }
}
----

===== Mocking the Context

For complex routes that interact with forms, bodies, or headers, you can provide a javadoc:test.MockContext[artifact=jooby-test] or a mock object from a library like Mockito.

.Using MockContext
[source,java,role="primary"]
----
@Test
public void testWithForm() {
  MockRouter router = new MockRouter(new App());
  MockContext context = new MockContext();
  
  context.setForm(Formdata.create(context).put("name", "Jooby"));
  
  assertEquals("Jooby", router.post("/", context).value());
}
----

.Kotlin
[source,kotlin,role="secondary"]
----
@Test
fun testWithForm() {
  val router = MockRouter(App())
  val context = MockContext().apply {
    form = Formdata.create(this).put("name", "Jooby")
  }
  
  assertEquals("Jooby", router.post("/", context).value())
}
----

==== Integration Testing

Integration tests run a real web server and allow you to test your application using any HTTP client. Jooby provides a JUnit 5 extension to manage the application lifecycle automatically.

1. **Add the Dependency:**
[dependency, artifactId="jooby-test"]

2. **Write the Test:**
Annotate your test class with `@JoobyTest`.

.Integration Test
[source,java,role="primary"]
----
import io.jooby.test.JoobyTest;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

@JoobyTest(App.class) // <1>
public class IntegrationTest {
  
  static OkHttpClient client = new OkHttpClient();

  @Test
  public void testApp() throws IOException {
    Request request = new Request.Builder()
        .url("http://localhost:8911") // <2>
        .build();

    try (Response response = client.newCall(request).execute()) {
      assertEquals("Easy testing!", response.body().string());
    }
  }
}
----

.Kotlin
[source,kotlin,role="secondary"]
----
import io.jooby.test.JoobyTest
import okhttp3.OkHttpClient
import okhttp3.Request

@JoobyTest(App::class) // <1>
class IntegrationTest {
  
  companion object {
    val client = OkHttpClient()
  }
  
  @Test
  fun testApp() {
    val request = Request.Builder()
        .url("http://localhost:8911") // <2>
        .build()

    client.newCall(request).execute().use { response ->
      assertEquals("Easy testing!", response.body?.string())
    }
  }
}
----

<1> Jooby starts the application before the test and stops it afterward.
<2> The default integration test port is `8911`.

===== Injecting Server Details

If you use a random port (`port = 0`) or want to avoid hardcoding URLs, you can inject server details directly into your test methods or fields:

.Injection Example
[source,java,role="primary"]
----
@JoobyTest(value = App.class, port = 0)
public void test(int serverPort, String serverPath) {
  // serverPort: e.g. 54321
  // serverPath: e.g. "http://localhost:54321"
}
----

.Kotlin
[source,kotlin,role="secondary"]
----
@JoobyTest(value = App::class, port = 0)
fun test(serverPort: Int, serverPath: String) {
  // serverPort: e.g. 54321
  // serverPath: e.g. "http://localhost:54321"
}
----

Supported injectable types include:
* `int serverPort`: The port the application is listening on.
* `String serverPath`: The full base URL (e.g., `http://localhost:port`).
* `io.jooby.Environment`: Access to the test environment.
* `com.typesafe.config.Config`: Access to the application configuration.
* `io.jooby.Jooby`: Access to the application instance itself.

[TIP]
====
When running integration tests, Jooby automatically sets the environment name to `test`. You can create a `conf/application.test.conf` file to provide test-specific settings.
====

===== Using a Factory Method

If your application requires constructor arguments, you can specify a `factoryMethod` to instantiate it:

.Factory Method Test
[source,java,role="primary"]
----
@JoobyTest(value = App.class, factoryMethod = "createApp")
public class TestApp {
  
  public static App createApp() {
    return new App("custom-argument");
  }
}
----

.Kotlin
[source,kotlin,role="secondary"]
----
@JoobyTest(value = App::class, factoryMethod = "createApp")
class TestApp {
  
  companion object {
    @JvmStatic
    fun createApp(): App = App("custom-argument")
  }
}
----
