//! Run with //! //! ```not_rust //! cargo test --example testing //! ``` use axum::{prelude::*, routing::BoxRoute}; use tower_http::trace::TraceLayer; #[tokio::main] async fn main() { // Set the RUST_LOG, if it hasn't been explicitly defined if std::env::var("RUST_LOG").is_err() { std::env::set_var("RUST_LOG", "testing=debug,tower_http=debug") } tracing_subscriber::fmt::init(); let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000)); tracing::debug!("listening on {}", addr); axum::Server::bind(&addr) .serve(app().into_make_service()) .await .unwrap(); } /// Having a function that produces our app makes it easy to call it from tests /// without having to create an HTTP server. #[allow(dead_code)] fn app() -> BoxRoute { route("/", get(|| async { "Hello, World!" })) .route( "/json", post(|payload: extract::Json| async move { response::Json(serde_json::json!({ "data": payload.0 })) }), ) // We can still add middleware .layer(TraceLayer::new_for_http()) .boxed() } #[cfg(test)] mod tests { use super::*; use http::StatusCode; use serde_json::{json, Value}; use std::net::{SocketAddr, TcpListener}; use tower::ServiceExt; // for `app.oneshot()` #[tokio::test] async fn hello_world() { let app = app(); // `BoxRoute` implements `tower::Service>` so we can // call it like any tower service, no need to run an HTTP server. let response = app .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap()) .await .unwrap(); assert_eq!(response.status(), StatusCode::OK); let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); assert_eq!(&body[..], b"Hello, World!"); } #[tokio::test] async fn json() { let app = app(); let response = app .oneshot( Request::builder() .method(http::Method::POST) .uri("/json") .header(http::header::CONTENT_TYPE, "application/json") .body(Body::from( serde_json::to_vec(&json!([1, 2, 3, 4])).unwrap(), )) .unwrap(), ) .await .unwrap(); assert_eq!(response.status(), StatusCode::OK); let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); let body: Value = serde_json::from_slice(&body).unwrap(); assert_eq!(body, json!({ "data": [1, 2, 3, 4] })); } #[tokio::test] async fn not_found() { let app = app(); let response = app .oneshot( Request::builder() .uri("/does-not-exist") .body(Body::empty()) .unwrap(), ) .await .unwrap(); assert_eq!(response.status(), StatusCode::NOT_FOUND); let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); assert!(body.is_empty()); } // You can also spawn a server and talk to it like any other HTTP server: #[tokio::test] async fn the_real_deal() { let listener = TcpListener::bind("0.0.0.0:0".parse::().unwrap()).unwrap(); let addr = listener.local_addr().unwrap(); tokio::spawn(async move { axum::Server::from_tcp(listener) .unwrap() .serve(app().into_make_service()) .await .unwrap(); }); let client = hyper::Client::new(); let response = client .request( Request::builder() .uri(format!("http://{}", addr)) .body(Body::empty()) .unwrap(), ) .await .unwrap(); let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); assert_eq!(&body[..], b"Hello, World!"); } }