Testing the API
Automated tests.
Learning Objectives
- Write an integration test for a route
##Testing without starting a server
Automated tests catch bugs before your users do. Actix Web includes the actix_web::test module so you can send fake requests straight into your app in memory, with no port, no network and no flakiness.
#A first test
Write the handler, then test it in the same file:
use actix_web::{get, App, HttpResponse, Responder};
#[get("/hello")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("hello")
}
#[cfg(test)]
mod tests {
use super::*;
use actix_web::{http::StatusCode, test};
#[actix_web::test]
async fn hello_returns_ok() {
let app = test::init_service(App::new().service(hello)).await;
let req = test::TestRequest::get().uri("/hello").to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(resp.status(), StatusCode::OK);
let body = test::read_body(resp).await;
assert_eq!(body, "hello");
}
}Run all tests with:
cargo test#The key pieces
#[actix_web::test]is like#[test]but starts an async runtime for you.test::init_servicebuilds yourAppas a callable service.TestRequestbuilds a request: choose the method, URI, headers and body.call_servicesends it and returns the response, so you can assert on status, headers and body.
#Testing JSON endpoints
use actix_web::test;
use serde_json::json;
let req = test::TestRequest::post()
.uri("/users")
.set_json(json!({ "name": "Ada" }))
.to_request();
let resp = test::call_service(&app, req).await;
assert!(resp.status().is_success());
let body: serde_json::Value = test::read_body_json(resp).await;
assert_eq!(body["name"], "Ada");#Testing with state
Register the same app_data you use in production, but with test values (for example an in-memory fake or a test database):
let app = test::init_service(
App::new()
.app_data(web::Data::new(AppState { app_name: "test".into() }))
.service(index),
)
.await;#Good habits
- Test both the happy path and the error cases (404, 400, 401).
- Keep an
fn configure(cfg: &mut web::ServiceConfig)that registers your routes, and call it from bothmainand your tests so they always match. - Keep tests small and independent; each one builds its own app.
Interactive Code Inspector
Select any highlighted line of code below to deeply inspect its mechanics, understand why it exists, and what happens if you change it.
"What If?" Experiment
Learning by doing. Modify the code to see what happens.
Break a test on purpose.
Change the expected text to "goodbye" and run cargo test.
The test fails and prints both the left and right values.
Explanation: Seeing a test fail teaches you how to read the failure output, which you will rely on when real bugs appear.
Common Mistakes
Anticipate and avoid these frequent pitfalls when working with this concept.
Using #[test] for an async test
Plain #[test] has no async runtime, so await cannot run.
Explanation: actix_web::test sets up the runtime that async code needs.
Forgetting to register app_data in the test app
The handler asks for web::Data<T> but the test App never provided it, so every request fails with 500.
Explanation: The test App must contain everything the real App provides, including state and middleware the handler relies on.
Practical Exercise
Test a 404
TaskWrite a test that requests a route that does not exist and asserts the status is 404.
Requirements
- Build an App with one route
- Request a different URI
- Assert StatusCode::NOT_FOUND
Need a hint?
Try solving the exercise in your local environment first. Once you have a solution, or if you get completely stuck, reveal the answer.
Real-World Application
Teams run these in-memory tests in CI on every pull request. They run in milliseconds, so thousands of checks cost nothing, and they catch broken routes, wrong status codes and missing auth before deployment.
Key Takeaways
- Use #[actix_web::test] and cargo test for async tests.
- init_service builds the app, TestRequest builds requests, call_service sends them.
- test::read_body and read_body_json help you assert on the response body.
- Share a configure function between main and tests so routes never drift apart.
Finished reading?
Mark this lesson as done to track your progress and earn XP.