1 //! Copied from https://github.com/tokio-rs/axum/blob/v0.2.5/examples/testing/src/main.rs
2
3 use axum::{
4 routing::{get, post},
5 Json, Router,
6 };
7 use tower_http::trace::TraceLayer;
8
9 #[tokio::main]
main()10 async fn main() {
11 // Set the RUST_LOG, if it hasn't been explicitly defined
12 if std::env::var_os("RUST_LOG").is_none() {
13 std::env::set_var("RUST_LOG", "example_testing=debug,tower_http=debug")
14 }
15 tracing_subscriber::fmt::init();
16
17 let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000));
18
19 tracing::debug!("listening on {}", addr);
20
21 axum::Server::bind(&addr)
22 .serve(app().into_make_service())
23 .await
24 .unwrap();
25 }
26
27 /// Having a function that produces our app makes it easy to call it from tests
28 /// without having to create an HTTP server.
29 #[allow(dead_code)]
app() -> Router30 fn app() -> Router {
31 Router::new()
32 .route("/", get(|| async { "Hello, World!" }))
33 .route(
34 "/json",
35 post(|payload: Json<serde_json::Value>| async move {
36 Json(serde_json::json!({ "data": payload.0 }))
37 }),
38 )
39 // We can still add middleware
40 .layer(TraceLayer::new_for_http())
41 }
42
43 #[cfg(test)]
44 mod tests {
45 use super::*;
46
47 #[cfg(not(target_os = "windows"))]
48 use std::net::{SocketAddr, TcpListener};
49
50 use axum::{
51 body::Body,
52 http::{self, Request, StatusCode},
53 };
54 use serde_json::{json, Value};
55 use tower::ServiceExt; // for `app.oneshot()`
56
57 #[tokio::test]
hello_world()58 async fn hello_world() {
59 let app = app();
60
61 // `Router` implements `tower::Service<Request<Body>>` so we can
62 // call it like any tower service, no need to run an HTTP server.
63 let response = app
64 .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
65 .await
66 .unwrap();
67
68 assert_eq!(response.status(), StatusCode::OK);
69
70 let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
71 assert_eq!(&body[..], b"Hello, World!");
72 }
73
74 #[tokio::test]
json()75 async fn json() {
76 let app = app();
77
78 let response = app
79 .oneshot(
80 Request::builder()
81 .method(http::Method::POST)
82 .uri("/json")
83 .header(http::header::CONTENT_TYPE, mime::APPLICATION_JSON.as_ref())
84 .body(Body::from(
85 serde_json::to_vec(&json!([1_i8, 2_i8, 3_i8, 4_i8])).unwrap(),
86 ))
87 .unwrap(),
88 )
89 .await
90 .unwrap();
91
92 assert_eq!(response.status(), StatusCode::OK);
93
94 let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
95 let body: Value = serde_json::from_slice(&body).unwrap();
96 assert_eq!(body, json!({ "data": [1_i8, 2_i8, 3_i8, 4_i8] }));
97 }
98
99 #[tokio::test]
not_found()100 async fn not_found() {
101 let app = app();
102
103 let response = app
104 .oneshot(
105 Request::builder()
106 .uri("/does-not-exist")
107 .body(Body::empty())
108 .unwrap(),
109 )
110 .await
111 .unwrap();
112
113 assert_eq!(response.status(), StatusCode::NOT_FOUND);
114 let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
115 assert!(body.is_empty());
116 }
117
118 // TODO: This test fails on Windows, it shouldn't but it's unclear to me
119 // if this is an issue on the host or with the test.
120 #[cfg(not(target_os = "windows"))]
121 // You can also spawn a server and talk to it like any other HTTP server:
122 #[tokio::test]
the_real_deal()123 async fn the_real_deal() {
124 let listener = TcpListener::bind("0.0.0.0:0".parse::<SocketAddr>().unwrap()).unwrap();
125 let addr = listener.local_addr().unwrap();
126
127 tokio::spawn(async move {
128 axum::Server::from_tcp(listener)
129 .unwrap()
130 .serve(app().into_make_service())
131 .await
132 .unwrap();
133 });
134
135 let client = hyper::Client::new();
136
137 let response = client
138 .request(
139 Request::builder()
140 .uri(format!("http://{addr}"))
141 .body(Body::empty())
142 .unwrap(),
143 )
144 .await
145 .unwrap();
146
147 let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
148 assert_eq!(&body[..], b"Hello, World!");
149 }
150 }
151