diff --git a/content/docs/testing.md b/content/docs/testing.md index 2db98df..5f7b59d 100644 --- a/content/docs/testing.md +++ b/content/docs/testing.md @@ -14,33 +14,10 @@ integration tests. For unit testing, actix provides a request builder type and a simple handler runner. [*TestRequest*](../../actix-web/actix_web/test/struct.TestRequest.html) implements a builder-like pattern. -You can generate a `HttpRequest` instance with `finish()`, or you can -run your handler with `run()` or `run_async()`. +You can generate a `HttpRequest` instance with `to_http_request()`, or you can +run your handler with `block_on()`. -```rust -use actix_web::{http, test, HttpRequest, HttpResponse, HttpMessage}; - -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() - } - } - HttpResponse::BadRequest().into() -} - -fn main() { - let resp = test::TestRequest::with_header("content-type", "text/plain") - .run(&index) - .unwrap(); - assert_eq!(resp.status(), http::StatusCode::OK); - - let resp = test::TestRequest::default() - .run(&index) - .unwrap(); - assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST); -} -``` +{{< include-example example="testing" file="main.rs" section="unit-tests" >}} # Integration tests @@ -58,146 +35,26 @@ for this function is a *test application* instance. > Check the [api documentation](../../actix-web/actix_web/test/struct.TestApp.html) > for more information. -```rust -use actix_web::{HttpRequest, HttpMessage}; -use actix_web::test::TestServer; -use std::str; - -fn index(req: &HttpRequest) -> &'static str { - "Hello world!" -} - -fn main() { - // start new test server - let mut srv = TestServer::new(|app| app.handler(index)); - - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - let bytes = srv.execute(response.body()).unwrap(); - let body = str::from_utf8(&bytes).unwrap(); - assert_eq!(body, "Hello world!"); -} -``` +{{< include-example example="testing" file="integration_one.rs" section="integration-one" >}} The other option is to use an application factory. In this case, you need to pass the factory function the same way as you would for real http server configuration. -```rust -use actix_web::{http, test, App, HttpRequest, HttpResponse}; - -fn index(req: &HttpRequest) -> HttpResponse { - HttpResponse::Ok().into() -} - -/// This function get called by http server. -fn create_app() -> App { - App::new() - .resource("/test", |r| r.h(index)) -} - -fn main() { - let mut srv = test::TestServer::with_factory(create_app); - - let request = srv.client( - http::Method::GET, "/test").finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - - assert!(response.status().is_success()); -} -``` +{{< include-example example="testing" file="integration_two.rs" section="integration-two" >}} If you need more complex application configuration, use the `TestServer::build_with_state()` method. For example, you may need to initialize application state or start `SyncActor`'s for diesel interation. This method accepts a closure that constructs the application state, and it runs when the actix system is configured. Thus, you can initialize any additional actors. -```rust -#[test] -fn test() { - let srv = TestServer::build_with_state(|| { - // we can start diesel actors - let addr = SyncArbiter::start(3, || { - DbExecutor(SqliteConnection::establish("test.db").unwrap()) - }); - // then we can construct custom state, or it could be `()` - MyState{addr: addr} - }) - - // register server handlers and start test server - .start(|app| { - app.resource( - "/{username}/index.html", |r| r.with( - |p: Path| format!("Welcome {}!", p.username))); - }); - - // now we can run our test code -); -``` - +{{< include-example example="testing" file="integration_three.rs" section="integration-three" >}} # Stream response tests If you need to test stream it would be enough to convert a [*ClientResponse*](../../actix-web/actix_web/client/struct.ClientResponse.html) to future and execute it. For example of testing [*Server Sent Events*](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events). -```rust -extern crate bytes; -extern crate futures; -extern crate actix_web; - -use bytes::Bytes; -use futures::stream::poll_fn; -use futures::{Async, Poll, Stream}; - -use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse}; -use actix_web::http::{ContentEncoding, StatusCode}; -use actix_web::test::TestServer; - - -fn sse(_req: &HttpRequest) -> HttpResponse { - let mut counter = 5usize; - // yields `data: N` where N in [5; 1] - let server_events = poll_fn(move || -> Poll, Error> { - if counter == 0 { - return Ok(Async::Ready(None)); - } - let payload = format!("data: {}\n\n", counter); - counter -= 1; - Ok(Async::Ready(Some(Bytes::from(payload)))) - }); - - HttpResponse::build(StatusCode::OK) - .content_encoding(ContentEncoding::Identity) - .content_type("text/event-stream") - .streaming(server_events) -} - - -fn main() { - // start new test server - let mut srv = TestServer::new(|app| app.handler(sse)); - - // request stream - let request = srv.get().finish().unwrap(); - let response = srv.execute(request.send()).unwrap(); - assert!(response.status().is_success()); - - // convert ClientResponse to future, start read body and wait first chunk - let mut stream = response.payload(); - loop { - match srv.execute(stream.into_future()) { - Ok((Some(bytes), remain)) => { - println!("{:?}", bytes); - stream = remain - } - Ok((None, _)) => break, - Err(_) => panic!(), - } - } -} -``` +{{< include-example example="testing" file="stream_response.rs" section="stream-response" >}} # WebSocket server tests @@ -209,34 +66,4 @@ result of the future computation. The following example demonstrates how to test a websocket handler: -```rust -use actix::{Actor, StreamHandler}; -use actix_web::*; -use futures::Stream; - -struct Ws; // <- WebSocket actor - -impl Actor for Ws { - type Context = ws::WebsocketContext; -} - -impl StreamHandler for Ws { - fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { - match msg { - ws::Message::Text(text) => ctx.text(text), - _ => (), - } - } -} - -fn main() { - let mut srv = test::TestServer::new( - |app| app.handler(|req| ws::start(req, Ws))); - - let (reader, mut writer) = srv.ws().unwrap(); - writer.text("text"); - - let (item, reader) = srv.execute(reader.into_future()).unwrap(); - assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); -} -``` +{{< include-example example="testing" file="websockets.rs" section="web-socket" >}} diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 6b54cd8..94df1cc 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -20,4 +20,5 @@ exclude = [ "errors", "requests", "responses", + "testing", ] diff --git a/examples/testing/Cargo.toml b/examples/testing/Cargo.toml new file mode 100644 index 0000000..d9b43d6 --- /dev/null +++ b/examples/testing/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "testing" +version = "0.1.0" +authors = ["Cameron Dershem "] +edition = "2018" + +[dependencies] +actix-web = "1.0" +futures = "0.1" +bytes = "0.4" diff --git a/examples/testing/src/integration_one.rs b/examples/testing/src/integration_one.rs new file mode 100644 index 0000000..5507d7d --- /dev/null +++ b/examples/testing/src/integration_one.rs @@ -0,0 +1,22 @@ +// +// use actix_web::test::TestServer; +// use actix_web::HttpRequest; +// use std::str; + +// fn index(req: HttpRequest) -> &'static str { +// "Hello world!" +// } + +// fn main() { +// // start new test server +// let mut srv = TestServer::new(|app| app.handler(index)); + +// let request = srv.get().finish().unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// let bytes = srv.execute(response.body()).unwrap(); +// let body = str::from_utf8(&bytes).unwrap(); +// assert_eq!(body, "Hello world!"); +// } +// diff --git a/examples/testing/src/integration_three.rs b/examples/testing/src/integration_three.rs new file mode 100644 index 0000000..1373b32 --- /dev/null +++ b/examples/testing/src/integration_three.rs @@ -0,0 +1,21 @@ +// +// #[test] +// fn test() { +// let srv = TestServer::build_with_state(|| { +// // we can start diesel actors +// let addr = SyncArbiter::start(3, || { +// DbExecutor(SqliteConnection::establish("test.db").unwrap()) +// }); +// // then we can construct custom state, or it could be `()` +// MyState{addr: addr} +// }) + +// // register server handlers and start test server +// .start(|app| { +// app.resource( +// "/{username}/index.html", |r| r.with( +// |p: Path| format!("Welcome {}!", p.username))); +// }); +// // now we can run our test code +// ); +// diff --git a/examples/testing/src/integration_two.rs b/examples/testing/src/integration_two.rs new file mode 100644 index 0000000..b5920f7 --- /dev/null +++ b/examples/testing/src/integration_two.rs @@ -0,0 +1,21 @@ +// +// use actix_web::{http, test, App, HttpRequest, HttpResponse}; + +// fn index(req: &HttpRequest) -> HttpResponse { +// HttpResponse::Ok().into() +// } + +// /// This function get called by http server. +// fn create_app() -> App { +// App::new().resource("/test", |r| r.h(index)) +// } + +// fn main() { +// let mut srv = test::TestServer::with_factory(create_app); + +// let request = srv.client(http::Method::GET, "/test").finish().unwrap(); +// let response = srv.execute(request.send()).unwrap(); + +// assert!(response.status().is_success()); +// } +// diff --git a/examples/testing/src/main.rs b/examples/testing/src/main.rs new file mode 100644 index 0000000..82b8bc4 --- /dev/null +++ b/examples/testing/src/main.rs @@ -0,0 +1,29 @@ +mod integration_one; +mod integration_three; +mod integration_two; +mod stream_response; +mod websockets; +// +use actix_web::{http, test, HttpRequest, HttpResponse}; + +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(); + } + } + HttpResponse::BadRequest().into() +} + +fn main() { + let req = + test::TestRequest::with_header("content-type", "text/plain").to_http_request(); + + let resp = test::block_on(index(req)).unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + + let req = test::TestRequest::default().to_http_request(); + let resp = test::block_on(index(req)).unwrap(); + assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST); +} +// diff --git a/examples/testing/src/stream_response.rs b/examples/testing/src/stream_response.rs new file mode 100644 index 0000000..9fea603 --- /dev/null +++ b/examples/testing/src/stream_response.rs @@ -0,0 +1,50 @@ +// +// use bytes::Bytes; +// use futures::stream::poll_fn; +// use futures::{Async, Poll, Stream}; + +// use actix_web::http::{ContentEncoding, StatusCode}; +// use actix_web::test::TestServer; +// use actix_web::{Error, HttpRequest, HttpResponse}; + +// fn sse(_req: &HttpRequest) -> HttpResponse { +// let mut counter = 5usize; +// // yields `data: N` where N in [5; 1] +// let server_events = poll_fn(move || -> Poll, Error> { +// if counter == 0 { +// return Ok(Async::Ready(None)); +// } +// let payload = format!("data: {}\n\n", counter); +// counter -= 1; +// Ok(Async::Ready(Some(Bytes::from(payload)))) +// }); + +// HttpResponse::build(StatusCode::OK) +// .content_encoding(ContentEncoding::Identity) +// .content_type("text/event-stream") +// .streaming(server_events) +// } + +// fn main() { +// // start new test server +// let mut srv = TestServer::new(|app| app.handler(sse)); + +// // request stream +// let request = srv.get().finish().unwrap(); +// let response = srv.execute(request.send()).unwrap(); +// assert!(response.status().is_success()); + +// // convert ClientResponse to future, start read body and wait first chunk +// let mut stream = response.payload(); +// loop { +// match srv.execute(stream.into_future()) { +// Ok((Some(bytes), remain)) => { +// println!("{:?}", bytes); +// stream = remain +// } +// Ok((None, _)) => break, +// Err(_) => panic!(), +// } +// } +// } +// diff --git a/examples/testing/src/websockets.rs b/examples/testing/src/websockets.rs new file mode 100644 index 0000000..8034116 --- /dev/null +++ b/examples/testing/src/websockets.rs @@ -0,0 +1,30 @@ +// +use actix::{Actor, StreamHandler}; +use actix_web::*; +use futures::Stream; + +struct Ws; // <- WebSocket actor + +impl Actor for Ws { + type Context = ws::WebsocketContext; +} + +impl StreamHandler for Ws { + fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { + match msg { + ws::Message::Text(text) => ctx.text(text), + _ => (), + } + } +} + +fn main() { + let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws))); + + let (reader, mut writer) = srv.ws().unwrap(); + writer.text("text"); + + let (item, reader) = srv.execute(reader.into_future()).unwrap(); + assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); +} +//