1
0
mirror of https://github.com/actix/actix-website synced 2025-02-08 22:36:07 +01:00

update testing to actix-web 2.0.0 and improve stream testing code (#141)

* update testing to actix-web 2.0.0 and improve stream testing code
This commit is contained in:
Evgeniy Tatarkin 2020-01-22 00:40:37 +03:00 committed by Yuki Okushi
parent 13f0756d33
commit bc4e0422c8
6 changed files with 71 additions and 67 deletions

View File

@ -11,10 +11,9 @@ integration tests.
# Unit Tests # Unit Tests
For unit testing, actix-web provides a request builder type and a simple handler runner. For unit testing, actix-web provides a request builder type.
[*TestRequest*][testrequest] implements a builder-like pattern. You can generate a [*TestRequest*][testrequest] implements a builder-like pattern. You can generate a
`HttpRequest` instance with `to_http_request()`, or you can `HttpRequest` instance with `to_http_request()` and call your handler with it.
run your handler with `block_on()`.
{{< include-example example="testing" file="main.rs" section="unit-tests" >}} {{< 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 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. 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. 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 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" >}} {{< 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 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 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. `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 # 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. to future and execute it.
For example of testing [*Server Sent Events*][serversentevents]. For example of testing [*Server Sent Events*][serversentevents].
{{< include-example example="testing" file="stream_response.rs" section="stream-response" >}} {{< 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 [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 [responsebody]: https://docs.rs/actix-web/2/actix_web/body/enum.ResponseBody.html
[actixdocs]: https://docs.rs/actix-web/1/actix_web/test/index.html [actixdocs]: https://docs.rs/actix-web/2/actix_web/test/index.html
[testrequest]: https://docs.rs/actix-web/1/actix_web/error/trait.ResponseError.html#foreign-impls [testrequest]: https://docs.rs/actix-web/2/actix_web/error/trait.ResponseError.html#foreign-impls

View File

@ -4,8 +4,10 @@ version = "1.0.0"
edition = "2018" edition = "2018"
[dependencies] [dependencies]
actix-web = "1.0" actix-web = "2.0"
futures = "0.1" actix-rt = "1.0"
bytes = "0.4" futures = "0.3"
futures-util = "0.3"
bytes = "0.5"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"

View File

@ -1,7 +1,7 @@
use actix_web::{HttpRequest, Responder}; use actix_web::{HttpRequest, Responder};
#[allow(dead_code)] #[allow(dead_code)]
fn index(_req: HttpRequest) -> impl Responder { async fn index(_req: HttpRequest) -> impl Responder {
"Hello world!" "Hello world!"
} }
@ -9,24 +9,21 @@ fn index(_req: HttpRequest) -> impl Responder {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use actix_web::dev::Service;
use actix_web::{test, web, App}; use actix_web::{test, web, App};
#[test] #[actix_rt::test]
fn test_index_get() { async fn test_index_get() {
let mut app = test::init_service(App::new().route("/", web::get().to(index))); let mut app = test::init_service(App::new().route("/", web::get().to(index))).await;
let req = test::TestRequest::get().uri("/").to_request(); let req = test::TestRequest::with_header("content-type", "text/plain").to_request();
let resp = test::block_on(app.call(req)).unwrap(); let resp = test::call_service(&mut app, req).await;
assert!(resp.status().is_success()); assert!(resp.status().is_success());
} }
#[test] #[actix_rt::test]
fn test_index_post() { async fn test_index_post() {
let mut app = test::init_service(App::new().route("/", web::get().to(index))); let mut app = test::init_service(App::new().route("/", web::get().to(index))).await;
let req = test::TestRequest::post().uri("/").to_request(); 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()); assert!(resp.status().is_client_error());
} }
} }

View File

@ -7,7 +7,7 @@ struct AppState {
} }
#[allow(dead_code)] #[allow(dead_code)]
fn index(data: web::Data<AppState>) -> impl Responder { async fn index(data: web::Data<AppState>) -> impl Responder {
HttpResponse::Ok().json(data.get_ref()) HttpResponse::Ok().json(data.get_ref())
} }
@ -17,17 +17,17 @@ mod tests {
use super::*; use super::*;
use actix_web::{test, web, App}; use actix_web::{test, web, App};
#[test] #[actix_rt::test]
fn test_index_get() { async fn test_index_get() {
let mut app = test::init_service( let mut app = test::init_service(
App::new() App::new()
.data(AppState { count: 4 }) .data(AppState { count: 4 })
.route("/", web::get().to(index)), .route("/", web::get().to(index)),
); ).await;
let req = test::TestRequest::get().uri("/").to_request(); 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);
} }
} }
// </integration-two> // </integration-two>

View File

@ -3,7 +3,7 @@ pub mod integration_two;
pub mod stream_response; pub mod stream_response;
use actix_web::{http, web, App, HttpRequest, HttpResponse}; 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 Some(hdr) = req.headers().get(http::header::CONTENT_TYPE) {
if let Ok(_s) = hdr.to_str() { if let Ok(_s) = hdr.to_str() {
return HttpResponse::Ok().into(); return HttpResponse::Ok().into();
@ -22,19 +22,17 @@ mod tests {
use super::*; use super::*;
use actix_web::test; use actix_web::test;
#[test] #[actix_rt::test]
fn test_index_ok() { async fn test_index_ok() {
let req = test::TestRequest::with_header("content-type", "text/plain") let req = test::TestRequest::with_header("content-type", "text/plain").to_http_request();
.to_http_request(); let resp = index(req).await;
let resp = test::block_on(index(req)).unwrap();
assert_eq!(resp.status(), http::StatusCode::OK); assert_eq!(resp.status(), http::StatusCode::OK);
} }
#[test] #[actix_rt::test]
fn test_index_not_ok() { async fn test_index_not_ok() {
let req = test::TestRequest::default().to_http_request(); 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); assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST);
} }
} }

View File

@ -1,27 +1,30 @@
// <stream-response> // <stream-response>
use std::task::Poll;
use bytes::Bytes; use bytes::Bytes;
use futures::stream::poll_fn; use futures::stream::poll_fn;
use futures::{Async, Poll};
use actix_web::http::{ContentEncoding, StatusCode}; 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; let mut counter: usize = 5;
// yields `data: N` where N in [5; 1] // yields `data: N` where N in [5; 1]
let server_events = poll_fn(move || -> Poll<Option<Bytes>, Error> { let server_events = poll_fn(move |_cx| -> Poll<Option<Result<Bytes, Error>>> {
if counter == 0 { if counter == 0 {
return Ok(Async::Ready(None)); return Poll::Ready(None);
} }
let payload = format!("data: {}\n\n", counter); let payload = format!("data: {}\n\n", counter);
counter -= 1; counter -= 1;
Ok(Async::Ready(Some(Bytes::from(payload)))) Poll::Ready(Some(Ok(Bytes::from(payload))))
}); });
HttpResponse::build(StatusCode::OK) HttpResponse::build(StatusCode::OK)
.encoding(ContentEncoding::Identity) .set_header(http::header::CONTENT_TYPE, "text/event-stream")
.content_type("text/event-stream") .set_header(
http::header::CONTENT_ENCODING,
ContentEncoding::Identity.as_str(),
)
.streaming(server_events) .streaming(server_events)
} }
@ -32,18 +35,32 @@ pub fn main() {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use actix_rt;
use futures_util::stream::StreamExt;
use futures_util::stream::TryStreamExt;
use actix_web::{test, web, App}; use actix_web::{test, web, App};
#[test] #[actix_rt::test]
fn test_stream() { async fn test_stream() {
let mut app = test::init_service(App::new().route("/", web::get().to(sse))); let mut app = test::init_service(App::new().route("/", web::get().to(sse))).await;
let req = test::TestRequest::get().to_request(); let req = test::TestRequest::get().to_request();
let resp = test::read_response(&mut app, req);
assert!( let mut resp = test::call_service(&mut app, req).await;
resp == Bytes::from_static( assert!(resp.status().is_success());
b"data: 5\n\ndata: 4\n\ndata: 3\n\ndata: 2\n\ndata: 1\n\n"
) // 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"));
} }
} }
// </stream-response> // </stream-response>