Skip to content
This repository was archived by the owner on Nov 13, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ edition = "2018"
[dependencies]
serde_json = "1.0"
tokio = { version = "1", features = ["full"] }
hyper = { version = "0.14", features = ["full"] }
hyper = { version = "1", features = ["full"] }
futures = "0.3"
async-trait = "0.1"
tower-http = "0.2"
tokio-test = "0.4.2"
test-context = "0.1.3"
test-context = "0.4.1"
lazy_static = "1.4"
queues = "1.1"
hyper-util = { version = "0.1.10", features = ["full"] }
http-body-util = "0.1.2"

[dev-dependencies]
serial_test = "0.6.0"
29 changes: 22 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,32 @@ A small test server utility to run http request against.
The test context instantiates a new server with a random port between 12300 and 12400. The test will use this port :

```rust,no_run
use test_context::{AsyncTestContext, test_context};
use hyper::{Uri, StatusCode, Client};
use tokiotest_httpserver::handler::{HandlerBuilder};
use http_body_util::combinators::BoxBody;
use hyper::body::Bytes;
use hyper::{StatusCode, Uri};
use hyper_util::{
client::legacy::{connect::HttpConnector, Client},
rt::TokioExecutor,
};
use std::convert::Infallible;
use test_context::{test_context, AsyncTestContext};
use tokiotest_httpserver::handler::HandlerBuilder;
use tokiotest_httpserver::HttpTestContext;

#[test_context(HttpTestContext)]
#[tokio::test]
async fn test_get_respond_200(ctx: &mut HttpTestContext) {
ctx.add(HandlerBuilder::new("/ok").status_code(StatusCode::OK).build());

let resp = Client::new().get(ctx.uri("/ok")).await.unwrap();
ctx.add(
HandlerBuilder::new("/ok")
.status_code(StatusCode::OK)
.build(),
);

let resp = Client::builder(TokioExecutor::new())
.build::<_, BoxBody<Bytes, Infallible>>(HttpConnector::new())
.get(ctx.uri("/ok"))
.await
.unwrap();

assert_eq!(200, resp.status());
}
Expand All @@ -31,4 +46,4 @@ It is also possible to use it with a sequential workflow. You just have to inclu

With serial workflow you can choose to use a fixed port for the http test server by setting the environment variable `TOKIOTEST_HTTP_PORT` to the desired port.

See for example [test_serial](tests/test_serial.rs).
See for example [test_serial](tests/test_serial.rs).
55 changes: 39 additions & 16 deletions src/handler.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
use crate::StatusCode;
use futures::future::BoxFuture;
use http_body_util::combinators::BoxBody;
use hyper::body::{Bytes, Incoming};
use hyper::{HeaderMap, Method, Request};
use std::convert::Infallible;
use std::sync::Arc;
use futures::future::BoxFuture;
use hyper::{Body, HeaderMap, Method, Request};
use crate::StatusCode;

pub type Response = hyper::Response<hyper::Body>;
pub type HandlerCallback = Arc<dyn Fn(Request<hyper::Body>) -> BoxFuture<'static, Result<Response, Infallible>> + Send + Sync>;
pub type Response = hyper::Response<BoxBody<Bytes, Infallible>>;
pub type HandlerCallback = Arc<
dyn Fn(Request<Incoming>) -> BoxFuture<'static, Result<Response, Infallible>> + Send + Sync,
>;

#[derive(Default, Clone)]
pub struct HandlerBuilder {
path: String,
method: Method,
headers: HeaderMap,
status_code: StatusCode
status_code: StatusCode,
}

#[allow(dead_code)]
Expand All @@ -22,7 +26,7 @@ impl HandlerBuilder {
path: String::from(path),
method: Method::GET,
headers: HeaderMap::new(),
status_code: StatusCode::INTERNAL_SERVER_ERROR
status_code: StatusCode::INTERNAL_SERVER_ERROR,
}
}

Expand All @@ -42,23 +46,39 @@ impl HandlerBuilder {
}

pub fn build(self) -> HandlerCallback {
let Self { path, method, status_code, headers } = self;
Arc::new(move |req: Request<Body>| {
let Self {
path,
method,
status_code,
headers,
} = self;
Arc::new(move |req: Request<Incoming>| {
let cloned_path = path.clone();
let cloned_method = method.clone();
let cloned_headers = headers.clone();
Box::pin(async move {
if req.uri().path().eq(cloned_path.as_str()) && req.method().eq(&cloned_method)
&& Self::contains_headers(req.headers(), &cloned_headers) {
Ok(hyper::Response::builder().status(status_code).body(Body::empty()).unwrap())
if req.uri().path().eq(cloned_path.as_str())
&& req.method().eq(&cloned_method)
&& Self::contains_headers(req.headers(), &cloned_headers)
{
Ok(hyper::Response::builder()
.status(status_code)
.body(BoxBody::default())
.unwrap())
} else {
Ok(hyper::Response::builder().status(StatusCode::INTERNAL_SERVER_ERROR).body(Body::empty()).unwrap())
Ok(hyper::Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(BoxBody::default())
.unwrap())
}
})
})
}

fn contains_headers(headers_reference: &HeaderMap, headers_to_be_contained: &HeaderMap) -> bool {
fn contains_headers(
headers_reference: &HeaderMap,
headers_to_be_contained: &HeaderMap,
) -> bool {
for (header, value) in headers_to_be_contained {
if !headers_reference.get(header).eq(&Some(value)) {
return false;
Expand All @@ -68,6 +88,9 @@ impl HandlerBuilder {
}
}

pub async fn default_handle(_req: Request<Body>) -> Result<Response, Infallible> {
Ok(hyper::Response::builder().status(StatusCode::INTERNAL_SERVER_ERROR).body(Body::empty()).unwrap())
pub async fn default_handle(_req: Request<Incoming>) -> Result<Response, Infallible> {
Ok(hyper::Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(BoxBody::default())
.unwrap())
}
Loading