Axum – testing API endpoints
Let’s look at different types of routes in Axum and how to create them AND test them.
As an Aide-mémoire let’s create something I/we can refer back to.
Webdock – Fast Cloud VPS Linux Hosting
Aim :
List the types of endpoint, and show how to test them with cURL, and within the Rust code itself.
If you’re new to Axum, check out timClicks tour of Axum
And also look up David Pedersen, the creator of Axum.
3 common requests:
1 .Path Extract : https://docs.rs/axum/latest/axum/extract/struct.Path.html
2 .Query Extract : https://docs.rs/axum/latest/axum/extract/struct.Query.html
3. JSON Extract https://docs.rs/axum/latest/axum/struct.Json.html
I’ll post the full code at the end, but lets focus on the key line of code for each of the above.
For a more comprehensive list, check the Axum Docs
Example Code :
Path Extract
https://docs.rs/axum/latest/axum/extract/struct.Path.html#example
let app = Router::new().route("/users/:user_id", get(user_info));
curl -X GET http://localhost:3000/users/{insert_valid_uuid_here}
curl -X GET http://localhost:3000/users/550e8400-e29b-41d4-a716-446655440000
Query Extract
https://docs.rs/axum/latest/axum/extract/struct.Query.html#example-1
let app = Router::new().route("/list_things", get(list_things));
let result: Query<ExampleParams> = Query::try_from_uri(&uri).unwrap();
curl -X GET "http://example.com/path?foo=hello&bar=42"
More Axum Query Extract examples
Webdock – Fast Cloud VPS Linux Hosting
JSON Extract
.route("/json", get(json_handler))
async fn json_handler(Query(params): Query<HashMap<String, String>>) -> Json<Value> {
info!("handling request");
let q: Option<&String> = params.get("q");
Json(json!({
"message": "Hello, Internet!",
"q": q,
}) )
}
❯ curl -X GET "http://0.0.0.0:3000/json?q=your_query_string"
{"message":"Hello, Internet!","q":"your_query_string"}%
POST JSON
What if we want to post multiple keys and values to the API endpoint?
curl -X POST -H "Content-Type: application/json" -d '{"name": "John Doe", "age": 25, "iq":100}' http://localhost:8000/post
Full code :
#![allow(unused)]
use axum::{
async_trait,
extract::{FromRef, FromRequestParts, State},
http::{request::Parts, StatusCode},
response::IntoResponse,
routing::{get, post},
Router,
};
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct MyData {
name: String,
age: u32,
iq: u8,
}
async fn handle_post(data: axum::extract::Json<MyData>) -> impl IntoResponse {
let my_data = data.0;
println!("Received data: {:?}", my_data);
(StatusCode::OK, "Data received successfully")
}
#[tokio::main]
async fn main() {
// Create an Axum router
let app = Router::new().route("/post", post(handle_post));
// Start the server
axum::Server::bind(&"0.0.0.0:8000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
[package]
name = "axumpost"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
axum = "0.6"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1.0"
❯ cargo run
Compiling axumpost v0.1.0 (/home/rag/Documents/rust/axumpost)
Finished dev [unoptimized + debuginfo] target(s) in 1.94s
Running `/home/rag/Documents/rust/axumpost/target/debug/axumpost`
Received data: MyData { name: "John Doe", age: 25, iq: 100 }
The key part here is :
axum::extract::Json<MyData>
Where MyData is your struct in JSON format
Test based on official Axum example code:
https://github.com/tokio-rs/axum/tree/main/examples/parse-body-based-on-content-type
We’lll add code to enable use to test the main.rs with reqwest, whenever we run “cargo test”
We’ll need to add “reqwest” crate, and then the code below at the bottom of main.rs as follows:
[tokio::test] ~ cargo test
Add this code into main :
#[tokio::test]
async fn test_endpoint() {
// Assuming your server is running on port 3000
let url = "http://127.0.0.1:3000";
// Test with JSON payload
let json_payload = serde_json::json!({"foo": "test"});
let response = reqwest::Client::new()
.post(url)
.json(&json_payload)
.send()
.await
.expect("Failed to send request");
assert!(response.status().is_success());
// Test with form payload
let form_payload = [("foo", "test")];
let response = reqwest::Client::new()
.post(url)
.form(&form_payload)
.send()
.await
.expect("Failed to send request");
assert!(response.status().is_success());
}
Be sure to specify “json” as a feature when you add reqwest:
reqwest = { version = "0.11", features = ["json"] }
[dependencies]
axum = { path = "../../axum" }
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.108"
tokio = { version = "1.0", features = ["full"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
do “cargo run” in one terminal, then in a new terminal from same directory, run “cargo test“
1 passed!
Nice!
Conclusion
We’ve tested Axum endpoints with cURL and using cargo test and “reqwest” crate!
Official Axum usage example : https://github.com/tokio-rs/axum/tree/main#usage-example