diff --git a/content/docs/testing.md b/content/docs/testing.md index 292a558..c59c923 100644 --- a/content/docs/testing.md +++ b/content/docs/testing.md @@ -11,10 +11,9 @@ integration tests. # Unit Tests -For unit testing, actix-web provides a request builder type and a simple handler runner. -[*TestRequest*][testrequest] implements a builder-like pattern. You can generate a -`HttpRequest` instance with `to_http_request()`, or you can -run your handler with `block_on()`. +For unit testing, actix-web provides a request builder type. +[*TestRequest*][testrequest] implements a builder-like pattern. You can generate a +`HttpRequest` instance with `to_http_request()` and call your handler with it. {{< include-example example="testing" file="main.rs" section="unit-tests" >}} @@ -23,7 +22,7 @@ run your handler with `block_on()`. There a few methods for testing your application. Actix-web can be used to run the application with specific handlers in a real http server. -`TestRequest::get()`, `TestRequest::post()`, and `TestRequest::client()` +`TestRequest::get()`, `TestRequest::post()` and other methods can be used to send requests to the test server. To create a `Service` for testing, use the `test::init_service` method which accepts a @@ -33,15 +32,6 @@ regular `App` builder. {{< include-example example="testing" file="integration_one.rs" section="integration-one" >}} -Note: If you get the error message "no Task is currently running" when running your -test, you may need to wrap your service call in -[test::run_on](https://docs.rs/actix-web/1/actix_web/test/fn.run_on.html): - -```rust -let future = test::run_on(|| app.call(req)); -let resp = test::block_on(future).unwrap(); -``` - If you need more complex application configuration testing should be very similar to creating the normal application. For example, you may need to initialize application state. Create an `App` with a `data` method and attach state just like you would from a normal application. @@ -50,13 +40,13 @@ the normal application. For example, you may need to initialize application stat # Stream response tests -If you need to test stream it would be enough to convert a [*ClientResponse*][clientresponse] +If you need to test stream it would be enough to call `take_body()` and convert a resulting [*ResponseBody*][responsebody] to future and execute it. For example of testing [*Server Sent Events*][serversentevents]. {{< include-example example="testing" file="stream_response.rs" section="stream-response" >}} [serversentevents]: https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events -[clientresponse]: https://docs.rs/actix-web/1/actix_web/client/struct.ClientResponse.html -[actixdocs]: https://docs.rs/actix-web/1/actix_web/test/index.html -[testrequest]: https://docs.rs/actix-web/1/actix_web/error/trait.ResponseError.html#foreign-impls +[responsebody]: https://docs.rs/actix-web/2/actix_web/body/enum.ResponseBody.html +[actixdocs]: https://docs.rs/actix-web/2/actix_web/test/index.html +[testrequest]: https://docs.rs/actix-web/2/actix_web/error/trait.ResponseError.html#foreign-impls diff --git a/examples/testing/Cargo.toml b/examples/testing/Cargo.toml index 5eba424..bc3a0d6 100644 --- a/examples/testing/Cargo.toml +++ b/examples/testing/Cargo.toml @@ -4,8 +4,10 @@ version = "1.0.0" edition = "2018" [dependencies] -actix-web = "1.0" -futures = "0.1" -bytes = "0.4" +actix-web = "2.0" +actix-rt = "1.0" +futures = "0.3" +futures-util = "0.3" +bytes = "0.5" serde = "1.0" serde_json = "1.0" diff --git a/examples/testing/src/integration_one.rs b/examples/testing/src/integration_one.rs index 750cbc9..223655a 100644 --- a/examples/testing/src/integration_one.rs +++ b/examples/testing/src/integration_one.rs @@ -1,7 +1,7 @@ use actix_web::{HttpRequest, Responder}; #[allow(dead_code)] -fn index(_req: HttpRequest) -> impl Responder { +async fn index(_req: HttpRequest) -> impl Responder { "Hello world!" } @@ -9,24 +9,21 @@ fn index(_req: HttpRequest) -> impl Responder { #[cfg(test)] mod tests { use super::*; - use actix_web::dev::Service; use actix_web::{test, web, App}; - #[test] - fn test_index_get() { - let mut app = test::init_service(App::new().route("/", web::get().to(index))); - let req = test::TestRequest::get().uri("/").to_request(); - let resp = test::block_on(app.call(req)).unwrap(); - + #[actix_rt::test] + async fn test_index_get() { + let mut app = test::init_service(App::new().route("/", web::get().to(index))).await; + let req = test::TestRequest::with_header("content-type", "text/plain").to_request(); + let resp = test::call_service(&mut app, req).await; assert!(resp.status().is_success()); } - #[test] - fn test_index_post() { - let mut app = test::init_service(App::new().route("/", web::get().to(index))); + #[actix_rt::test] + async fn test_index_post() { + let mut app = test::init_service(App::new().route("/", web::get().to(index))).await; let req = test::TestRequest::post().uri("/").to_request(); - let resp = test::block_on(app.call(req)).unwrap(); - + let resp = test::call_service(&mut app, req).await; assert!(resp.status().is_client_error()); } } diff --git a/examples/testing/src/integration_two.rs b/examples/testing/src/integration_two.rs index 0c5e164..9808d95 100644 --- a/examples/testing/src/integration_two.rs +++ b/examples/testing/src/integration_two.rs @@ -7,7 +7,7 @@ struct AppState { } #[allow(dead_code)] -fn index(data: web::Data) -> impl Responder { +async fn index(data: web::Data) -> impl Responder { HttpResponse::Ok().json(data.get_ref()) } @@ -17,17 +17,17 @@ mod tests { use super::*; use actix_web::{test, web, App}; - #[test] - fn test_index_get() { + #[actix_rt::test] + async fn test_index_get() { let mut app = test::init_service( App::new() .data(AppState { count: 4 }) .route("/", web::get().to(index)), - ); + ).await; let req = test::TestRequest::get().uri("/").to_request(); - let resp: AppState = test::read_response_json(&mut app, req); + let resp: AppState = test::read_response_json(&mut app, req).await; - assert!(resp.count == 4); + assert_eq!(resp.count, 4); } } // diff --git a/examples/testing/src/main.rs b/examples/testing/src/main.rs index f1780fb..49625f4 100644 --- a/examples/testing/src/main.rs +++ b/examples/testing/src/main.rs @@ -3,7 +3,7 @@ pub mod integration_two; pub mod stream_response; use actix_web::{http, web, App, HttpRequest, HttpResponse}; -fn index(req: HttpRequest) -> HttpResponse { +async fn index(req: HttpRequest) -> HttpResponse { if let Some(hdr) = req.headers().get(http::header::CONTENT_TYPE) { if let Ok(_s) = hdr.to_str() { return HttpResponse::Ok().into(); @@ -22,19 +22,17 @@ mod tests { use super::*; use actix_web::test; - #[test] - fn test_index_ok() { - let req = test::TestRequest::with_header("content-type", "text/plain") - .to_http_request(); - - let resp = test::block_on(index(req)).unwrap(); + #[actix_rt::test] + async fn test_index_ok() { + let req = test::TestRequest::with_header("content-type", "text/plain").to_http_request(); + let resp = index(req).await; assert_eq!(resp.status(), http::StatusCode::OK); } - #[test] - fn test_index_not_ok() { + #[actix_rt::test] + async fn test_index_not_ok() { let req = test::TestRequest::default().to_http_request(); - let resp = test::block_on(index(req)).unwrap(); + let resp = index(req).await; assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST); } } diff --git a/examples/testing/src/stream_response.rs b/examples/testing/src/stream_response.rs index f922e05..7f608af 100644 --- a/examples/testing/src/stream_response.rs +++ b/examples/testing/src/stream_response.rs @@ -1,27 +1,30 @@ // +use std::task::Poll; use bytes::Bytes; use futures::stream::poll_fn; -use futures::{Async, Poll}; use actix_web::http::{ContentEncoding, StatusCode}; -use actix_web::{middleware::BodyEncoding, web, App, Error, HttpRequest, HttpResponse}; +use actix_web::{web, http, App, Error, HttpRequest, HttpResponse}; -fn sse(_req: HttpRequest) -> HttpResponse { +async fn sse(_req: HttpRequest) -> HttpResponse { let mut counter: usize = 5; // yields `data: N` where N in [5; 1] - let server_events = poll_fn(move || -> Poll, Error> { + let server_events = poll_fn(move |_cx| -> Poll>> { if counter == 0 { - return Ok(Async::Ready(None)); + return Poll::Ready(None); } let payload = format!("data: {}\n\n", counter); counter -= 1; - Ok(Async::Ready(Some(Bytes::from(payload)))) + Poll::Ready(Some(Ok(Bytes::from(payload)))) }); HttpResponse::build(StatusCode::OK) - .encoding(ContentEncoding::Identity) - .content_type("text/event-stream") + .set_header(http::header::CONTENT_TYPE, "text/event-stream") + .set_header( + http::header::CONTENT_ENCODING, + ContentEncoding::Identity.as_str(), + ) .streaming(server_events) } @@ -32,18 +35,32 @@ pub fn main() { #[cfg(test)] mod tests { use super::*; + use actix_rt; + + use futures_util::stream::StreamExt; + use futures_util::stream::TryStreamExt; + use actix_web::{test, web, App}; - #[test] - fn test_stream() { - let mut app = test::init_service(App::new().route("/", web::get().to(sse))); + #[actix_rt::test] + async fn test_stream() { + let mut app = test::init_service(App::new().route("/", web::get().to(sse))).await; let req = test::TestRequest::get().to_request(); - let resp = test::read_response(&mut app, req); - assert!( - resp == Bytes::from_static( - b"data: 5\n\ndata: 4\n\ndata: 3\n\ndata: 2\n\ndata: 1\n\n" - ) - ); + + let mut resp = test::call_service(&mut app, req).await; + assert!(resp.status().is_success()); + + // first chunk + let (bytes, mut resp) = resp.take_body().into_future().await; + assert_eq!(bytes.unwrap().unwrap(), Bytes::from_static(b"data: 5\n\n")); + + // second chunk + let (bytes, mut resp) = resp.take_body().into_future().await; + assert_eq!(bytes.unwrap().unwrap(), Bytes::from_static(b"data: 4\n\n")); + + // remaining part + let bytes = test::load_stream(resp.take_body().into_stream()).await; + assert_eq!(bytes.unwrap(), Bytes::from_static(b"data: 3\n\ndata: 2\n\ndata: 1\n\n")); } } //