Testing Your Handlers
This is where Luxis delivers on its promise. The same test code runs against an in-memory stub and a real Vert.x HTTP server. You write your tests once, and you choose the execution mode.
Two modes, one test
Section titled “Two modes, one test”| In-memory (stub) | Real server (Vert.x) | |
|---|---|---|
| Factory | Luxis.test(routes) | Luxis.start(routes) |
| Speed | Milliseconds | Seconds |
| Network | None — everything runs in-process | Real HTTP over localhost |
| When to use | Day-to-day development, CI | Final verification, debugging network-level issues |
Both give you a TestClient with the same API. Your test code doesn’t know or care which mode it’s running in.
Register your routes once
Section titled “Register your routes once”Define your routes in a method. This is the same function your production server calls:
public static MyState registerRoutes(RoutesRegister routesRegister) { MyState state = new MyState(); routesRegister.jsonRoute("/echo", Method.POST, state, EchoRequest.class, new PostEchoHandler()); routesRegister.jsonRoute("/echo", Method.GET, state, Void.class, new GetEchoHandler()); return state;}In production:
Luxis.start(MyApp::registerRoutes);Creating a test client
Section titled “Creating a test client”In-memory (stub)
Section titled “In-memory (stub)”Luxis<MyState> luxis = Luxis.test(MyApp::registerRoutes);TestClient client = new StubTestClient("127.0.0.1", 8080, luxis);No server starts. No ports are opened. Your handlers execute synchronously in-process.
Real server (Vert.x)
Section titled “Real server (Vert.x)”Luxis<MyState> luxis = Luxis.start(MyApp::registerRoutes, new WebServiceConfigBuilder().setPort(8080).build());TestClient client = new VertxTestClient("127.0.0.1", 8080);A real HTTP server starts on port 8080. Requests go over the network through the full Vert.x stack.
The test code is identical
Section titled “The test code is identical”From this point on, the code is the same regardless of which mode you chose:
TestHttpResponse response = client.post( StubRequest.request("/echo") .body(json().put("name", "Alice").toString()));
Assert.assertEquals( TestHttpResponse.response(expectedJson), response);Same request builder. Same response type. Same assertions. The only difference is how you created the Luxis instance — one line.
A complete test
Section titled “A complete test”public class EchoTest {
private Luxis<MyState> luxis; private TestClient client;
@Before public void setUp() { luxis = Luxis.test(MyApp::registerRoutes); client = new StubTestClient("127.0.0.1", 8080, luxis); }
@After public void tearDown() throws Exception { client.assertNoMoreExceptions(); client.close(); luxis.close(); }
@Test public void shouldEchoBackJsonValues() { String requestBody = json() .put("intExample", 17) .put("stringExample", "hiya") .toString();
TestHttpResponse response = client.post( StubRequest.request("/echo").body(requestBody));
String expectedResponse = json() .put("intExample", 17) .put("stringExample", "hiya") .toString();
Assert.assertEquals( TestHttpResponse.response(expectedResponse), response); }}To run this same test against the real Vert.x server, change two lines in setUp():
@Beforepublic void setUp() { luxis = Luxis.start(MyApp::registerRoutes, new WebServiceConfigBuilder().setPort(8080).build()); client = new VertxTestClient("127.0.0.1", 8080);}Everything else stays exactly the same.
Building requests
Section titled “Building requests”Use StubRequest to build test requests fluently. This works in both modes:
StubRequest.request("/echo") .body("{\"name\": \"test\"}") .queryParam("search", "hello") .headerParam("X-Request-Id", "abc") .cookie("session", "xyz") .fileUpload("document", "file contents")Building JSON test data
Section titled “Building JSON test data”TestHelper.json() returns a Jackson ObjectNode for fluent JSON construction:
import static io.kiw.luxis.web.test.TestHelper.json;
String body = json() .put("intExample", 17) .put("stringExample", "hiya") .putNull("optionalField") .toString();// {"intExample":17,"stringExample":"hiya","optionalField":null}Asserting responses
Section titled “Asserting responses”TestHttpResponse supports equality checks on body, status code, headers, and cookies:
TestHttpResponse expected = TestHttpResponse.response(expectedJson) .withStatusCode(400) .withHeader("X-Custom", "value") .withCookie(new HttpCookie("session", "abc"));
Assert.assertEquals(expected, actual);Testing across microservices
Section titled “Testing across microservices”The in-memory mode extends beyond a single service. You can wire multiple Luxis services together using LuxisHttpClient and test cross-service flows without any network:
// Create the downstream service in-memoryLuxis<DownstreamState> downstream = Luxis.test(DownstreamApp::registerRoutes);
// Create an HTTP client that talks to the downstream service in-memoryLuxisHttpClient httpClient = StubLuxisHttpClient.create(downstream);
// Create the upstream service, injecting the HTTP clientLuxis<UpstreamState> upstream = Luxis.test(r -> { UpstreamState state = new UpstreamState(); r.jsonRoute("/call-next", Method.POST, state, HttpClientGetRequest.class, new HttpClientCallHandler(httpClient, "http://127.0.0.1:8091")); return state;});
TestClient client = new StubTestClient("127.0.0.1", 8090, upstream);
// Test the full chain — no network, no Docker, millisecondsTestHttpResponse response = client.post( StubRequest.request("/call-next") .body(json().put("targetPath", "/api/value").toString()));In stub mode, all of this runs in-memory. To switch to real HTTP, replace Luxis.test with Luxis.start and StubLuxisHttpClient with VertxLuxisHttpClient. The test assertions don’t change.
Exception handling in tests
Section titled “Exception handling in tests”In stub mode, exceptions thrown inside handlers are captured and can be asserted:
TestHttpResponse response = client.post( StubRequest.request("/throw") .body(json().put("where", "complete").toString()));
Assert.assertEquals( TestHttpResponse.response(json().put("message", "Something went wrong").toString()) .withStatusCode(500), response);
// Assert the exception was capturedclient.assertException("app error in complete");
// At teardown, verify no unexpected exceptions occurredclient.assertNoMoreExceptions();