Crate asserhttp

source ·
Expand description

Allows fluent assertions for various http client responses. Supports actix-web, rocket, reqwest, hyper, awc (Actix Web Client), surf and isahc.

It works for blocking or async client methods and for responses wrapped in Result.

§API

Here’s the list of all the provided asserters.

use asserhttp::*;

#[test]
fn my_test() {
    // status
    .expect_status(200)
    .expect_status(Status::Ok)
    .expect_status_in_range(200, 400)
    .expect_status_success()
    .expect_status_redirection()
    .expect_status_client_error()
    .expect_status_server_error()
    .expect_status_ok()
    .expect_status_created()
    .expect_status_accepted()
    .expect_status_no_content()
    .expect_status_partial_content()
    .expect_status_bad_request()
    .expect_status_unauthorized()
    .expect_status_forbidden()
    .expect_status_not_found()
    .expect_status_conflict()
    .expect_status_gone()
    .expect_status_internal_server_error()
    // header
    .expect_header("content-type", "application/pdf")
    .expect_header(headers::CONTENT_TYPE, "application/pdf")
    .expect_header("content-type", |h| assert_eq!(h, "application/pdf"))
    .expect_headers("cache-control", ["no-cache", "no-store"])
    .expect_headers(headers::CACHE_CONTROL, ["no-cache", "no-store"])
    .expect_headers("cache-control", |h: Vec<&str>| assert!(h.contains(&"a") && h.contains(&"b")))
    .expect_header_present("x-my-header")
    .expect_header_present(headers::CONTENT_TYPE)
    .expect_header_absent("x-my-header")
    .expect_header_absent(headers::ACCEPT)
    .expect_content_type_json()
    .expect_content_type_text()
    // body
    .expect_body_json(|b: Value| assert_eq!(b, json!({"a": "b"})))
    .expect_body_json_eq(json!({"name": "jdoe"}))
    .expect_body_text(|b| assert_eq!(b, "abcd"))
    .expect_body_text_eq("abcd")
    .expect_body_text_matches("[a-z]+")
    .expect_body_bytes(|b| assert_eq!(b, b"abcd"))
    .expect_body_bytes_eq(b"abcd")
    .expect_body_present()
    .expect_body_absent();
}

§Example

§actix-web

Use actix feature.

For unit testing

use actix_web::{HttpRequest, HttpResponse, test::TestRequest};
use serde_json::json;
use asserhttp::*;

#[actix_web::test]
async fn sample_test() {
    async fn handler(_: HttpRequest) -> HttpResponse { HttpResponse::Ok().body(json!({"a": "b"})) }
    handler(TestRequest::get().to_http_request()).await
        .expect_status_ok()
        .expect_content_type_json()
        .expect_body_json_eq(json!({"a": "b"}));
    // and many more !
}

For integration tests

use actix_web::{App, HttpResponse, test::{call_service, init_service, TestRequest}, web};
use serde_json::json;
use asserhttp::*;

#[actix_web::test]
async fn sample_test() {
    let app = App::new().route("/", web::get().to(|| async { HttpResponse::Ok().body(json!({"a": "b"})) }));
    call_service(&mut init_service(app).await, TestRequest::get().to_request()).await
        .expect_status_ok()
        .expect_content_type_json()
        .expect_body_json_eq(json!({"a": "b"}));
    // and many more !
}

§rocket

Use rocket feature.

use rocket::{get, http::Status, local::asynchronous::Client, routes};
use serde_json::{json, Value};
use asserhttp::*;

#[rocket::async_test]
async fn sample_test() {
    #[get("/")]
    fn endpoint() -> Value { json!({"a": "b"}) }
    let client = Client::tracked(rocket::build().mount("/", routes![endpoint])).await.unwrap();
    client.get("/").dispatch().await
        .expect_status_ok()
        .expect_content_type_json()
        .expect_body_json_eq(json!({"a": "b"}));
}

§reqwest

Use reqwest feature.

use reqwest;
use asserhttp::*;
use serde_json::json;

#[tokio::test]
async fn sample_test() {
    reqwest::get("http://localhost/api/any").await.unwrap().expect_status_ok();
    // no need to call `.unwrap()` directly
    reqwest::get("http://localhost/api/any").await.expect_status(200);
    reqwest::get("http://localhost/api/any").await.expect_status_ok();
    reqwest::get("http://localhost/api/any").await.expect_status_bad_request();
    reqwest::get("http://localhost/api/any").await.expect_status_internal_server_error();
    // chain expectations
    reqwest::get("http://localhost/api/any").await
        .expect_status_ok()
        .expect_content_type_json()
        .expect_body_json_eq(json!({"name": "jdoe"}));
    // and many more !
}

§hyper

Use hyper feature.

use hyper::Client;
use asserhttp::*;
use serde_json::json;

#[tokio::test]
async fn sample_test() {
    Client::new().get("http://localhost/api/any").await.unwrap().expect_status_ok();
    // no need to call `.unwrap()` directly
    Client::new().get("http://localhost/api/any").await.expect_status(200);
    Client::new().get("http://localhost/api/any").await.expect_status_ok();
    Client::new().get("http://localhost/api/any").await.expect_status_bad_request();
    Client::new().get("http://localhost/api/any").await.expect_status_internal_server_error();
    // chain expectations
    Client::new().get("http://localhost/api/any").await
        .expect_status_ok()
        .expect_content_type_json()
        .expect_body_json_eq(json!({"name": "jdoe"}));
    // and many more !
}

§axum

Use axum feature.

use asserhttp::*;
use serde_json::json;
use tower::ServiceExt as _;

#[tokio::test]
async fn sample_test() {
    let app = axum::Router::new().route("/", axum::routing::get( || async move {
        let mut axum_response = axum::response::Response::builder()
            .status(axum::http::StatusCode::OK)
            .body(json!({"a": "b"}).to_string())
            .unwrap();
        axum_response.headers_mut().insert("content-type", "application/json".parse().unwrap());
        axum_response
    }));
    let req = axum::http::Request::builder().method(axum::http::Method::GET).uri("/").body(axum::body::Body::empty()).unwrap();
    app.oneshot(req).await
        .expect_status_ok()
        .expect_content_type_json()
        .expect_body_json_eq(json!({"name": "jdoe"}));
}

§awc (Actix Web Client)

Use actix-web-client feature.

use awc::Client;
use asserhttp::*;
use serde_json::json;

#[actix_web::test]
async fn sample_test() {
    Client::default().get("http://localhost/api/any").await.unwrap().expect_status_ok();
    // no need to call `.unwrap()` directly
    Client::default().get("http://localhost/api/any").await.expect_status(200);
    Client::default().get("http://localhost/api/any").await.expect_status_ok();
    Client::default().get("http://localhost/api/any").await.expect_status_bad_request();
    Client::default().get("http://localhost/api/any").await.expect_status_internal_server_error();
    // chain expectations
    Client::default().get("http://localhost/api/any").await
        .expect_status_ok()
        .expect_content_type_json()
        .expect_body_json_eq(json!({"name": "jdoe"}));
    // and many more !
}

§surf

Use surf feature.

use surf;
use asserhttp::*;
use serde_json::json;

#[tokio::test]
async fn sample_test() {
    surf::get("http://localhost/api/any").await.unwrap().expect_status_ok();
    // no need to call `.unwrap()` directly
    surf::get("http://localhost/api/any").await.expect_status(200);
    surf::get("http://localhost/api/any").await.expect_status_ok();
    surf::get("http://localhost/api/any").await.expect_status_bad_request();
    surf::get("http://localhost/api/any").await.expect_status_internal_server_error();
    // chain expectations
    surf::get("http://localhost/api/any").await
        .expect_status_ok()
        .expect_content_type_json()
        .expect_body_json_eq(json!({"name": "jdoe"}));
    // and many more !
}

§ureq

Use ureq feature.

use ureq::OrAnyStatus;
use asserhttp::*;
use serde_json::json;

#[tokio::test]
async fn sample_test() {
    ureq::get("http://localhost/api/any").call().or_any_status().unwrap().expect_status_ok();
    // no need to call `.unwrap()` directly
    ureq::get("http://localhost/api/any").call().or_any_status().expect_status(200);
    ureq::get("http://localhost/api/any").call().or_any_status().expect_status_ok();
    ureq::get("http://localhost/api/any").call().or_any_status().expect_status_bad_request();
    ureq::get("http://localhost/api/any").call().or_any_status().expect_status_internal_server_error();
    // chain expectations
    ureq::get("http://localhost/api/any").call().or_any_status()
        .expect_status_ok()
        .expect_content_type_json()
        .expect_body_json_eq(json!({"name": "jdoe"}));
    // and many more !
}

§isahc

Use isahc feature.

use isahc;
use asserhttp::*;
use serde_json::json;

#[tokio::test]
async fn sample_test() {
    isahc::get_async("http://localhost/api/any").await.unwrap().expect_status_ok();
    // no need to call `.unwrap()` directly
    isahc::get_async("http://localhost/api/any").await.expect_status(200);
    isahc::get_async("http://localhost/api/any").await.expect_status_ok();
    isahc::get_async("http://localhost/api/any").await.expect_status_bad_request();
    isahc::get_async("http://localhost/api/any").await.expect_status_internal_server_error();
    // chain expectations
    isahc::get_async("http://localhost/api/any").await
        .expect_status_ok()
        .expect_content_type_json()
        .expect_body_json_eq(json!({"name": "jdoe"}));
    // and many more !
}

§Errors

In case you would want to use asserhttp in a non-test context (be aware though that it has not been written with performance in mind) you likely do not want it to panic. In that case you can use the (non-default) fallible feature which will bring in some try_* variants of the aforementioned methods, all of them yielding an AsserhttpError instead of panicking

use asserhttp::*;

#[tokio::test]
async fn my_test() {
    assert!(matches!(AsserhttpError::StatusMismatch { actual: 200, expected: 201 }, isahc::get_async("http://localhost/api/any").await.try_expect_status(201)));
}

Modules§

Macros§

Structs§

Enums§

Traits§

Type Aliases§