Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when adding, changing, testing, or debugging Rust HTTP APIs and services, especially when Codex needs black-box integration tests, random-port app startup, real database test isolation, external HTTP mocks, or CI-ready cargo verification for Actix, Axum, Warp, Rocket, or similar Rust web frameworks.
.claude/skills/hashgraph-online-rust-api-test-harness/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-11 | ✗→✓ | ▲ Improved | 10% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 35% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 36% | 0% |
| case-06 | ✓→✓ | = Same ✓ | 22% | 0% |
| case-07 | ✓→✓ | = Same ✓ | 29% | 0% |
Use this skill to make Rust API work test-first, reproducible, and CI-ready. Prefer black-box tests that exercise the service through HTTP and verify observable behavior rather than reaching through framework internals.
Cargo.toml, workspace layout, existing src/main.rs,src/lib.rs, router/startup modules, tests/, migrations, Docker Compose, and CI workflows.
if needed so tests can bind a random local port and pass explicit state.
under tests/ unless the project already has a clear convention.
spawn_app helper that starts the service once per test case,captures the base URL, and owns any disposable test resources.
reqwest, not framework-specificrequest objects, unless the project intentionally uses in-process tests.
Postgres for SQL behavior, a mock HTTP server for external APIs, and unique resource names per test.
hide boilerplate behind small helper methods, and avoid sleeps when readiness can be observed.
finishing.
Prefer a startup function that accepts infrastructure instead of creating it inside the handler module. Actix can listen on a synchronous std::net::TcpListener:
rustpub fn run( listener: std::net::TcpListener, app_state: AppState, ) -> Result<actix_web::dev::Server, std::io::Error> { let state = actix_web::web::Data::new(app_state); let server = actix_web::HttpServer::new(move || { actix_web::App::new() .app_data(state.clone()) .route("/health_check", actix_web::web::get().to(health_check)) }) .listen(listener)? .run(); Ok(server) }
For Axum, prefer the same boundary but use Tokio's async listener type:
rustpub fn router(app_state: AppState) -> axum::Router { axum::Router::new() .route("/health_check", axum::routing::get(health_check)) .with_state(app_state) } pub async fn spawn_axum_app() -> TestApp { let listener = tokio::net::TcpListener::bind("127.0.0.1:0") .await .expect("failed to bind random port"); let port = listener.local_addr().unwrap().port(); let server = axum::serve(listener, router(test_state().await)); let server_handle = tokio::spawn(async move { server.await }); TestApp { address: format!("http://127.0.0.1:{port}"), http_client: reqwest::Client::new(), server_handle, } }
Use a helper object instead of repeating setup in every test.
rustpub struct TestApp { pub address: String, pub http_client: reqwest::Client, pub server_handle: tokio::task::JoinHandle<std::io::Result<()>>, } pub async fn spawn_app() -> TestApp { let listener = std::net::TcpListener::bind("127.0.0.1:0") .expect("failed to bind random port"); let port = listener.local_addr().unwrap().port(); let server = my_app::startup::run(listener, test_state().await) .expect("failed to start test server"); let server_handle = tokio::spawn(server); TestApp { address: format!("http://127.0.0.1:{port}"), http_client: reqwest::Client::new(), server_handle, } }
Add small helper methods when a multi-step user journey appears in more than one test. Keep the method names behavior-oriented, for example post_subscription, login, create_newsletter, or confirm_subscription.
Do not discard the JoinHandle returned by tokio::spawn. Keep it in the test fixture so tests can detect an unexpectedly finished server task and abort it during teardown instead of hiding server-side panics behind later connection-refused errors.
database directly only when the public API has no observation point yet.
payloads, duplicate submissions, unauthorized users, upstream failures, and timeouts.
includes those exact bytes.
Read references/database-test-isolation.md when tests touch a database. Read references/external-http-mocks.md when tests call third-party HTTP APIs.
Default preferences:
substitutes.
write shared state.
wiremock or an equivalent local mock server for external HTTP APIs.Run the bundled preflight script from the Rust project root when the standard cargo workflow applies:
bashpath/to/rust-api-test-harness/scripts/check-rust-service.sh
By default, the script runs:
bashcargo fmt --all --check cargo clippy --all-targets --all-features -- -D warnings cargo test --all-features
Set RUST_API_TEST_HARNESS_SKIP_CLIPPY=1 to skip only the clippy step. The script still runs formatting and tests. Use this escape hatch when the project configures linting separately in CI or when --all-targets --all-features is known to be inappropriate for the local preflight.
If the project does not use all features in CI, mirror the repository's existing CI commands instead and explain the deviation.
references/database-test-isolation.md: patterns for Postgres-backed tests,migrations, per-test databases, and cleanup tradeoffs.
references/external-http-mocks.md: patterns for testing outbound HTTPclients and API workflows with local mocks.
Other measured skills in the registry, with their headline benchmark lift.