End-to-end testing is a crucial part of software development, ensuring that all components of an application work together as expected. In Rust, testing web applications can be streamlined using frameworks like Actix Web for server development and Reqwest for HTTP client requests. This tutorial provides a comprehensive guide to setting up and executing end-to-end tests for a Rust web application.

Prerequisites

  • Rust installed on your system (version 1.65+ recommended)
  • Basic knowledge of Rust programming
  • Understanding of asynchronous programming in Rust
  • Experience with web development concepts

Setting Up the Project

Create a new Rust project using Cargo:

cargo new rust_e2e_test --bin

Add dependencies to Cargo.toml:

[dependencies]
actix-web = "4"
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Creating a Sample Web Server

For testing purposes, we'll create a simple Actix Web server with a few endpoints.

use actix_web::{get, App, HttpServer, HttpResponse, Responder};

#[get("/hello")]
async fn hello() -> impl Responder {
    HttpResponse::Ok().body("Hello, world!")
}

#[get("/json")]
async fn json() -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({"status": "success"}))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(hello)
            .service(json)
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

Writing End-to-End Tests

Next, we'll write tests that start the server and make HTTP requests to verify responses.

#[cfg(test)]
mod tests {
    use super::*;
    use actix_web::test;
    use reqwest;

    #[tokio::test]
    async fn test_hello_endpoint() {
        // Start server in background
        let server = test::start(|| {
            App::new()
                .service(hello)
                .service(json)
        });

        // Send request to /hello
        let response = reqwest::get(&format!("{}/hello", server.url()))
            .await
            .expect("Failed to send request");

        assert_eq!(response.status(), reqwest::StatusCode::OK);
        let body = response.text().await.expect("Failed to read response body");
        assert_eq!(body, "Hello, world!");
    }

    #[tokio::test]
    async fn test_json_endpoint() {
        let server = test::start(|| {
            App::new()
                .service(hello)
                .service(json)
        });

        let response = reqwest::get(&format!("{}/json", server.url()))
            .await
            .expect("Failed to send request");

        assert_eq!(response.status(), reqwest::StatusCode::OK);
        let json_body: serde_json::Value = response.json().await.expect("Failed to parse JSON");
        assert_eq!(json_body["status"], "success");
    }
}

Running the Tests

Execute the tests using Cargo:

cargo test -- --test-threads=1

This command runs all tests asynchronously, but specifying --test-threads=1 ensures serial execution, which can be helpful for shared resources.

Conclusion

This tutorial demonstrated how to set up end-to-end testing for a Rust web application using Actix Web and Reqwest. By creating a test server and making HTTP requests, you can verify that your application's components work together correctly. Incorporate these testing strategies into your development workflow to improve reliability and maintainability.