Testing Guide
Draft

This guide is a work in progress and will be revised regularly until the official launch of Oorian.

1. Headless Page Rendering

Oorian's headless rendering infrastructure lets you test page output without deploying to a servlet container. The OorianTestHarness class in com.oorian.test provides a complete mock environment for rendering HttpFile subclasses (including HtmlPage) and capturing the generated HTML.

Setting Up the Test Harness

OorianTestHarness is the entry point for headless rendering. Call setup() before your tests to initialize the mock environment, and teardown() after your tests to clean up.

Java
OorianTestHarness harness = new OorianTestHarness();
harness.setup();

// ... run tests ...

harness.teardown();

Rendering Pages

The harness provides two render methods. The simple version takes an HttpFile and a path string, returning the rendered HTML directly. The advanced version accepts a TestHttpRequest for full control over request parameters, headers, and cookies, and returns a TestHttpResponse with access to the status code, headers, and content.

Java
// Simple render — returns HTML string
String html = harness.render(new HomePage(), "/");

// Render with custom request
TestHttpRequest request = harness.createRequest("/users/123");
request.addParameter("tab", "profile");
TestHttpResponse response = harness.render(new UserPage(), request);

String html = response.getContent();
int status = response.getStatus();

OorianTestHarness API

Method Description
setup() Initializes the test environment (mock context, session)
teardown() Cleans up the test environment
render(HttpFile, String) Renders a page at the given path, returns HTML string
render(HttpFile, TestHttpRequest) Renders with a custom request, returns TestHttpResponse
createRequest(String) Creates a pre-configured test request for the given path
createResponse() Creates an empty test response
getSession() Returns the shared test session
resetSession() Creates a new empty session (for test isolation)

Mock Classes

Class Implements Key Features
TestAppContext AppContext In-memory attributes and init parameters, configurable context path
TestHttpSession OorianHttpSession In-memory attributes, auto-generated session ID, invalidation tracking
TestHttpRequest OorianHttpRequest Configurable path, parameters, headers, cookies, locale, body content
TestHttpResponse OorianHttpResponse Captures content, status, headers, cookies, redirect URL

Session Persistence

The test harness maintains a single session across renders within a test setup, simulating real user behavior where a session persists as the user navigates between pages. Call resetSession() to create a fresh session for test isolation.

Java
// Session persists across renders
harness.render(new LoginPage(), "/login");
harness.getSession().setAttribute("user", testUser);
String html = harness.render(new DashboardPage(), "/dashboard");
// DashboardPage can access the "user" attribute

// Reset for a clean session
harness.resetSession();

2. Mock WebSocket Client

For testing server-side components that use WebSocket communication, Oorian provides mock implementations in the com.oorian.testing package. These mocks let you verify WebSocket messages, session attributes, and request handling without a servlet container or browser.

MockWebsocketConnection

Implements WebsocketConnection. Stores all sent messages in an ordered list for verification. Throws IllegalStateException if messages are sent after close.

Method Description
sendMessage(String) Stores message for later retrieval
close() Marks connection as closed
getMessages() Returns unmodifiable list of all sent messages
getLastMessage() Returns the most recently sent message
getMessageCount() Returns the number of messages sent
isClosed() Returns whether the connection has been closed
clearMessages() Resets the message history
Java
MockWebsocketConnection ws = new MockWebsocketConnection();
ws.sendMessage("hello");
ws.sendMessage("world");

ws.getMessageCount();   // 2
ws.getLastMessage();     // "world"
ws.getMessages();         // ["hello", "world"]

ws.close();
ws.isClosed();            // true
// ws.sendMessage("fail"); → IllegalStateException

MockOorianHttpSession

Implements OorianHttpSession. Uses an in-memory HashMap for attributes. Supports invalidation — all methods throw IllegalStateException after invalidate().

Method Description
MockOorianHttpSession() Creates session with random UUID
MockOorianHttpSession(String) Creates session with specified ID
setId(String) Changes the session ID
setNew(boolean) Sets the new-session flag
isInvalidated() Returns whether session was invalidated
getAttributeMap() Returns unmodifiable view of all attributes

MockOorianHttpRequest

Implements OorianHttpRequest. All properties are configurable via fluent setters that return this for chaining.

Property Default
Request URI /
Context Path "" (empty)
Method GET
Locale Locale.US
Remote Address 127.0.0.1
Server Name localhost
Scheme http
Server Port 8080
Java
MockOorianHttpRequest request = new MockOorianHttpRequest()
    .setRequestURI("/app/users/123")
    .setContextPath("/app")
    .setMethod("POST")
    .setContentType("application/json")
    .setBody("{\"name\": \"John\"}");

request.addHeader("Authorization", "Bearer token123");
request.addParameter("page", "1");

OorianTestContext

Convenience class that creates and wires together all mock objects, then registers the session to the current thread so OorianSession.get() works in tests.

Factory methods:

Method Description
create() Creates context with default mocks
create(Locale) Creates context with specified locale on the request
create(String, String) Creates context with specified URI and HTTP method

Accessor methods:

Method Description
getHttpSession() Returns the MockOorianHttpSession
getRequest() Returns the MockOorianHttpRequest
getWebsocketConnection() Returns the MockWebsocketConnection
getOorianSession() Returns the registered OorianSession
tearDown() Removes session from current thread (call in test cleanup)
Java
OorianTestContext ctx = OorianTestContext.create();

try
{
    OorianSession session = OorianSession.get();
    session.setAttribute("userId", 12345);

    MockWebsocketConnection ws = ctx.getWebsocketConnection();
    // ... trigger server-push logic ...
    List<String> messages = ws.getMessages();
}
finally
{
    ctx.tearDown();  // Clean up thread-local
}

3. Snapshot Testing

Snapshot testing detects unintended changes in component rendering by comparing current output against saved baselines. The SnapshotTester class in com.oorian.testing saves rendered HTML to .snap files on first run and verifies against those baselines on subsequent runs.

How It Works

The snapshot testing workflow follows three steps:

  1. First run: No .snap file exists — the current output is saved as the baseline.
  2. Subsequent runs: Output is compared against the saved snapshot.
  3. If they match, the test passes. If they differ, a SnapshotMismatchException is thrown.

SnapshotTester API

Method Description
SnapshotTester(String) Creates tester with snapshot directory path
SnapshotTester(Path) Creates tester with snapshot directory path
assertMatchesSnapshot(String, Element) Renders element and compares against saved snapshot
assertMatchesSnapshot(String, String) Compares raw string against saved snapshot
updateSnapshot(String, Element) Saves element's rendered HTML as new baseline
updateSnapshot(String, String) Saves string as new baseline
snapshotExists(String) Checks if a snapshot file exists
deleteSnapshot(String) Deletes a snapshot file
setUpdateMode(boolean) When true, overwrites mismatched snapshots instead of failing

SnapshotResult

Value Description
MATCH Current output matches the saved snapshot
MISMATCH Current output differs (thrown as exception unless update mode)
NEW_SNAPSHOT No snapshot existed; baseline was saved

Basic Usage

Java
SnapshotTester snapshots = new SnapshotTester("test/snapshots");

// Build a component
Div card = new Div();
card.addClass("user-card");
card.addElement(new H1("John Doe"));
card.addElement(new Paragraph("john@example.com"));

// First run: creates test/snapshots/user-card.snap
// Subsequent runs: compares against saved snapshot
snapshots.assertMatchesSnapshot("user-card", card);

Update Mode

When you intentionally change a component's output, enable update mode to refresh baselines instead of failing.

Java
SnapshotTester snapshots = new SnapshotTester("test/snapshots");
snapshots.setUpdateMode(true);

// Overwrites saved snapshot with current output
snapshots.assertMatchesSnapshot("user-card", card);

Handling Mismatches

Java
try
{
    snapshots.assertMatchesSnapshot("header", header);
}
catch (SnapshotMismatchException e)
{
    String expected = e.getExpected();     // saved content
    String actual = e.getActual();         // current content
    String name = e.getSnapshotName();     // "header"
}

4. Complete Example

Putting it all together. The following test class demonstrates headless page rendering, mock WebSocket verification, and snapshot testing used in combination.

Java
public class HomePageTest
{
    private OorianTestHarness harness;
    private OorianTestContext context;
    private SnapshotTester snapshots;

    public void setup()
    {
        harness = new OorianTestHarness();
        harness.setup();
        context = OorianTestContext.create();
        snapshots = new SnapshotTester("test/snapshots");
    }

    public void teardown()
    {
        context.tearDown();
        harness.teardown();
    }

    public void testHomePageRenders()
    {
        // Render the page
        String html = harness.render(new HomePage(), "/");

        // Verify the HTML matches the saved snapshot
        snapshots.assertMatchesSnapshot("home-page", html);
    }

    public void testHomePageWithSession()
    {
        // Set up session state
        OorianSession.get().setAttribute("user", "John");

        // Render with session context
        String html = harness.render(new HomePage(), "/");

        // Verify personalized content
        snapshots.assertMatchesSnapshot("home-page-logged-in", html);
    }

    public void testWebSocketMessages()
    {
        // Render the page
        harness.render(new DashboardPage(), "/dashboard");

        // Verify WebSocket messages sent during render
        MockWebsocketConnection ws = context.getWebsocketConnection();
        assert ws.getMessageCount() > 0;
    }

    public void testCustomRequest()
    {
        // Create a custom request
        TestHttpRequest request = harness.createRequest("/search");
        request.addParameter("q", "oorian");
        request.addHeader("Accept-Language", "de-DE");

        // Render with custom request
        TestHttpResponse response = harness.render(new SearchPage(), request);

        // Verify response
        assert response.getStatus() == 200;
        snapshots.assertMatchesSnapshot("search-results", response.getContent());
    }
}

API Summary

Class Package Purpose
OorianTestHarness com.oorian.test Headless page rendering and lifecycle
TestAppContext com.oorian.test Mock application context
TestHttpSession com.oorian.test Mock HTTP session
TestHttpRequest com.oorian.test Mock HTTP request
TestHttpResponse com.oorian.test Mock HTTP response
MockWebsocketConnection com.oorian.testing Mock WebSocket connection
MockOorianHttpSession com.oorian.testing Mock session with invalidation
MockOorianHttpRequest com.oorian.testing Configurable mock request
OorianTestContext com.oorian.testing Wires mocks and registers session
SnapshotTester com.oorian.testing Snapshot save/load/compare
SnapshotResult com.oorian.testing Enum: MATCH, MISMATCH, NEW_SNAPSHOT
SnapshotMismatchException com.oorian.testing Assertion error with expected/actual