Actix-Web Integration Testing

This project uses actix-web with the actix_web::test module for integration tests. All integration tests live in #[cfg(test)] mod tests blocks within the source files they test.

Core Pattern

Use test::init_service to spin up an in-memory App with your routes, then send requests with TestRequest:

#[cfg(test)]
mod tests {
    use actix_web::{http::StatusCode, test, App};
    use super::*;

    #[actix_web::test]
    async fn test_up_get() {
        let app = test::init_service(App::new().service(up)).await;
        let req = test::TestRequest::get().uri("/up").to_request();
        let resp = test::call_service(&app, req).await;
        assert_eq!(resp.status(), StatusCode::OK);
    }
}

Key Helpers

HelperPurpose
test::init_service(app)Build a test Service from an App
test::TestRequest::get()Build a GET request
test::TestRequest::post()Build a POST request
test::TestRequest::default()Build a request you customize (method, URI, headers, body)
.uri("/path")Set the request path
.insert_header(ContentType::plaintext())Add a header
.set_json(payload)Set a JSON body (serializes automatically)
.to_request()Finalize the TestRequest into a Request
.to_http_request()Finalize into an HttpRequest (for calling handlers directly)
test::call_service(&app, req)Send a request through the service and get a ServiceResponse
test::call_and_read_body(&app, req)Call service and read the body as Bytes
test::call_and_read_body_json(&app, req)Call service and deserialize the JSON body

Testing Responses

Check status codes:

let resp = test::call_service(&app, req).await;
assert!(resp.status().is_success());
assert_eq!(resp.status(), StatusCode::OK);
assert!(resp.status().is_client_error());

Assert body content:

let body = test::call_and_read_body(&app, req).await;
assert_eq!(&body, b"expected body");

Assert JSON responses:

let result: MyStruct = test::call_and_read_body_json(&app, req).await;
assert_eq!(result.field, expected_value);

Testing Handlers Directly (Unit Tests)

For standalone handlers (not using routing macros), call them with a HttpRequest built from TestRequest:

#[actix_web::test]
async fn test_handler_unit() {
    let req = test::TestRequest::default()
        .insert_header(ContentType::plaintext())
        .to_http_request();
    let resp = my_handler(req).await;
    assert_eq!(resp.status(), StatusCode::OK);
}

Testing with App State

If your app uses shared state via web::Data, include it in init_service:

#[actix_web::test]
async fn test_with_state() {
    let state = web::Data::new(AppState { count: 4 });
    let app = test::init_service(
        App::new()
            .app_data(state)
            .service(index),
    )
    .await;
    let resp = test::call_service(&app, test::TestRequest::get().uri("/").to_request()).await;
    assert!(resp.status().is_success());
}

Running Tests

just test       # cargo test
just verify     # fmt → check → clippy → test