Runtime

Testing

TestApp request helpers and provider overrides.

Testing

nidus-testing provides in-memory request helpers around Axum routers.

let response = TestApp::from_router(router).get("/health").send().await;
response.assert_status(http::StatusCode::OK);
response.assert_header("content-type", "text/plain; charset=utf-8");
response.assert_text("ok");

Request helpers are available for GET, POST, PUT, PATCH, and DELETE. Use request(Method, path) for other HTTP methods. Requests can set JSON, text, or raw byte bodies and custom headers:

let response = app
    .post("/users")
    .header("x-api-key", "secret")
    .json(&CreateUser { name: "Ada".to_owned() })
    .send()
    .await;

let head = app.request(http::Method::HEAD, "/health").send().await;

Use query() to append URL-encoded query parameters from any serializable test value:

let response = app
    .get("/users")
    .query(&ListUsers { page: 2 })
    .send()
    .await;

Use try_header() when tests should assert invalid header names or values without panicking:

let error = app
    .get("/health")
    .try_header("bad header", "secret")
    .unwrap_err();

Use try_json() when tests should assert JSON serialization failures without panicking. Use try_send() when tests should assert request construction failures without panicking.

Responses expose status(), headers(), header(name), fallible header_str(name), body(), text(), typed json(), fallible try_json(), and assertion helpers for status, headers, text, and JSON.

Use TestApp::bootstrap::<AppModule>() when a test should validate the Nidus module graph before applying overrides:

let app = TestApp::bootstrap::<AppModule>()?
    .override_provider(MockUsersRepository::new())?
    .build();

Use bootstrap_with_router() when the same test should validate the module graph and exercise HTTP routes:

let app = TestApp::bootstrap_with_router::<AppModule>(router)?
    .override_provider(MockUsersRepository::new())?
    .build();

For modular apps with imports, pass the imported module definitions explicitly:

let app = TestApp::bootstrap_with_modules::<AppModule, _>([
    UsersModule::definition(),
])?
.build();

Use bootstrap_with_modules_and_router() for imported module graphs that also need HTTP route coverage.

Provider and config overrides are configured through the builder:

let app = TestApp::builder(router)
    .provider(UsersRepository::new())?
    .transient_provider::<RequestId, _>(|_container| Ok(RequestId::new()))?
    .override_provider(MockUsersRepository::new())?
    .config(test_config)
    .build();

Built test apps attach the test Container as an Axum Extension<Arc<Container>>, so route handlers can resolve overridden providers from the same container used by app.resolve::<T>().

Request-lifetime providers can be registered with a factory and resolved through an explicit request scope:

let app = TestApp::builder(router)
    .request_provider::<RequestContext, _>(|_container| Ok(RequestContext::new()))?
    .build();

let scope = app.request_scope();
let context = scope.resolve::<RequestContext>()?;

Use request_scoped_provider() when a request-lifetime test provider depends on another request-lifetime provider:

let app = TestApp::builder(router)
    .request_provider::<RequestId, _>(|_container| Ok(RequestId::new()))?
    .request_scoped_provider::<RequestContext, _>(|scope| {
        Ok(RequestContext::new(scope.inject::<RequestId>()?))
    })?
    .build();

Lifecycle hooks can be started and shut down inside tests:

let app = TestApp::builder(router)
    .lifecycle_hook(DatabaseTestHook::new())
    .build_started()
    .await?;

app.shutdown().await?;