//! 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!");
}
}