Cookbook / Production
Testing your app
Call routes in-process, with no sockets and no ports, and assert on status, headers and body.
Server::into_app() builds the full pipeline (routing, middleware, error handling) so tests run requests through exactly what production runs, without binding a port. Write a function that builds your Server, and use it from both main and your tests.
use axeon::{Json, Server, ServerError};
use axeon::json;
use serde::Deserialize;
#[derive(Deserialize)]
struct Greeting {
name: String,
}
pub fn app() -> Server {
let mut app = Server::new();
app.get("/hello/:name", |req| async move {
format!("Hello, {}!", req.params()["name"])
});
app.post("/greet", |req| async move {
let greeting: Greeting = req.json()?;
Ok::<_, ServerError>((201, Json(json!({ "greeting": format!("Hi {}", greeting.name) }))))
});
app
}
fn main() {
app().listen("127.0.0.1:3000").expect("server failed to start");
}
#[cfg(test)]
mod tests {
use super::*;
use axeon::{Method, Request};
#[tokio::test]
async fn says_hello() {
let app = app().into_app().await.unwrap();
let res = app.call(Request::new(Method::GET, "/hello/Ada")).await;
assert_eq!(res.status, 200);
assert_eq!(res.body_text(), "Hello, Ada!");
}
#[tokio::test]
async fn greets_with_json() {
let app = app().into_app().await.unwrap();
let req = Request::new(Method::POST, "/greet").with_json(&json!({ "name": "Grace" }));
let res = app.call(req).await;
assert_eq!(res.status, 201);
assert!(res.body_text().contains("Hi Grace"));
}
#[tokio::test]
async fn rejects_bad_input() {
let app = app().into_app().await.unwrap();
let req = Request::new(Method::POST, "/greet").with_json(&json!({ "nope": 1 }));
assert_eq!(app.call(req).await.status, 422);
}
#[tokio::test]
async fn unknown_routes_are_404() {
let app = app().into_app().await.unwrap();
assert_eq!(app.call(Request::new(Method::GET, "/missing")).await.status, 404);
}
}
cargo test
Notes#
- Add
tokio = { version = "1", features = ["full"] }to[dev-dependencies]for#[tokio::test]. Request::new(..).with_header(..)and.with_body(content_type, bytes)build anything else you need, such as anAuthorizationheader.App::into_service()turns the app into a towerService, which is handy to mount inside another framework.