From 60f4f116cf3531180e5f1d78f0a3b50955430b04 Mon Sep 17 00:00:00 2001 From: Cameron Dershem Date: Mon, 24 Jun 2019 17:43:31 -0400 Subject: [PATCH] Testing is done-ish. --- content/docs/testing.md | 49 +++++-------- examples/testing/Cargo.toml | 2 + examples/testing/src/integration_one.rs | 43 +++++++----- examples/testing/src/integration_three.rs | 21 ------ examples/testing/src/integration_two.rs | 46 ++++++++----- examples/testing/src/main.rs | 40 +++++++---- examples/testing/src/stream_response.rs | 83 +++++++++++------------ examples/testing/src/websockets.rs | 30 -------- 8 files changed, 141 insertions(+), 173 deletions(-) delete mode 100644 examples/testing/src/integration_three.rs delete mode 100644 examples/testing/src/websockets.rs diff --git a/content/docs/testing.md b/content/docs/testing.md index 5f7b59d..335300b 100644 --- a/content/docs/testing.md +++ b/content/docs/testing.md @@ -6,13 +6,13 @@ weight: 210 # Testing -Every application should be well tested. Actix provides tools to perform unit and +Every application should be well tested. Actix-web provides tools to perform unit and integration tests. # Unit Tests -For unit testing, actix provides a request builder type and a simple handler runner. -[*TestRequest*](../../actix-web/actix_web/test/struct.TestRequest.html) +For unit testing, actix-web provides a request builder type and a simple handler runner. +[*TestRequest*](https://docs.rs/actix-web/1.0.2/actix_web/test/struct.TestRequest.html) implements a builder-like pattern. You can generate a `HttpRequest` instance with `to_http_request()`, or you can run your handler with `block_on()`. @@ -21,49 +21,32 @@ run your handler with `block_on()`. # Integration tests -There are several methods for testing your application. Actix provides -[*TestServer*](../../actix-web/actix_web/test/struct.TestServer.html), which 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. -`TestServer::get()`, `TestServer::post()`, and `TestServer::client()` +`TestRequest::get()`, `TestRequest::post()`, and `TestRequest::client()` methods can be used to send requests to the test server. -A simple form `TestServer` can be configured to use a handler. -`TestServer::new` method accepts a configuration function, and the only argument -for this function is a *test application* instance. +To create a `Service` for testing, use the `test::init_serivce` method which accepts a +regular `App` builder. -> Check the [api documentation](../../actix-web/actix_web/test/struct.TestApp.html) +> Check the [api documentation](https://docs.rs/actix-web/1.0.2/actix_web/test/index.html) > for more information. {{< 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. +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. {{< 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. - -{{< 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). +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). {{< include-example example="testing" file="stream_response.rs" section="stream-response" >}} - -# WebSocket server tests - -It is possible to register a *handler* with `TestApp::handler()`, which -initiates a web socket connection. *TestServer* provides the method `ws()`, which connects to -the websocket server and returns ws reader and writer objects. *TestServer* also -provides an `execute()` method, which runs future objects to completion and returns -result of the future computation. - -The following example demonstrates how to test a websocket handler: - -{{< include-example example="testing" file="websockets.rs" section="web-socket" >}} diff --git a/examples/testing/Cargo.toml b/examples/testing/Cargo.toml index 15532db..5eba424 100644 --- a/examples/testing/Cargo.toml +++ b/examples/testing/Cargo.toml @@ -7,3 +7,5 @@ edition = "2018" actix-web = "1.0" futures = "0.1" bytes = "0.4" +serde = "1.0" +serde_json = "1.0" diff --git a/examples/testing/src/integration_one.rs b/examples/testing/src/integration_one.rs index 5507d7d..750cbc9 100644 --- a/examples/testing/src/integration_one.rs +++ b/examples/testing/src/integration_one.rs @@ -1,22 +1,33 @@ +use actix_web::{HttpRequest, Responder}; + +#[allow(dead_code)] +fn index(_req: HttpRequest) -> impl Responder { + "Hello world!" +} + // -// use actix_web::test::TestServer; -// use actix_web::HttpRequest; -// use std::str; +#[cfg(test)] +mod tests { + use super::*; + use actix_web::dev::Service; + use actix_web::{test, web, App}; -// fn index(req: HttpRequest) -> &'static str { -// "Hello world!" -// } + #[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(); -// fn main() { -// // start new test server -// let mut srv = TestServer::new(|app| app.handler(index)); + assert!(resp.status().is_success()); + } -// let request = srv.get().finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); + #[test] + fn test_index_post() { + let mut app = test::init_service(App::new().route("/", web::get().to(index))); + let req = test::TestRequest::post().uri("/").to_request(); + let resp = test::block_on(app.call(req)).unwrap(); -// let bytes = srv.execute(response.body()).unwrap(); -// let body = str::from_utf8(&bytes).unwrap(); -// assert_eq!(body, "Hello world!"); -// } + assert!(resp.status().is_client_error()); + } +} // diff --git a/examples/testing/src/integration_three.rs b/examples/testing/src/integration_three.rs deleted file mode 100644 index 1373b32..0000000 --- a/examples/testing/src/integration_three.rs +++ /dev/null @@ -1,21 +0,0 @@ -// -// #[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 index b5920f7..0c5e164 100644 --- a/examples/testing/src/integration_two.rs +++ b/examples/testing/src/integration_two.rs @@ -1,21 +1,33 @@ +use actix_web::{web, HttpResponse, Responder}; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, Debug)] +struct AppState { + count: i32, +} + +#[allow(dead_code)] +fn index(data: web::Data) -> impl Responder { + HttpResponse::Ok().json(data.get_ref()) +} + // -// use actix_web::{http, test, App, HttpRequest, HttpResponse}; +#[cfg(test)] +mod tests { + use super::*; + use actix_web::{test, web, App}; -// fn index(req: &HttpRequest) -> HttpResponse { -// HttpResponse::Ok().into() -// } + #[test] + fn test_index_get() { + let mut app = test::init_service( + App::new() + .data(AppState { count: 4 }) + .route("/", web::get().to(index)), + ); + let req = test::TestRequest::get().uri("/").to_request(); + let resp: AppState = test::read_response_json(&mut app, req); -// /// 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()); -// } + assert!(resp.count == 4); + } +} // diff --git a/examples/testing/src/main.rs b/examples/testing/src/main.rs index 82b8bc4..f1780fb 100644 --- a/examples/testing/src/main.rs +++ b/examples/testing/src/main.rs @@ -1,10 +1,7 @@ -mod integration_one; -mod integration_three; -mod integration_two; -mod stream_response; -mod websockets; -// -use actix_web::{http, test, HttpRequest, HttpResponse}; +pub mod integration_one; +pub mod integration_two; +pub mod stream_response; +use actix_web::{http, web, App, HttpRequest, HttpResponse}; fn index(req: HttpRequest) -> HttpResponse { if let Some(hdr) = req.headers().get(http::header::CONTENT_TYPE) { @@ -16,14 +13,29 @@ fn index(req: HttpRequest) -> HttpResponse { } fn main() { - let req = - test::TestRequest::with_header("content-type", "text/plain").to_http_request(); + App::new().route("/", web::get().to(index)); +} - let resp = test::block_on(index(req)).unwrap(); - assert_eq!(resp.status(), http::StatusCode::OK); +// +#[cfg(test)] +mod tests { + use super::*; + use actix_web::test; - let req = test::TestRequest::default().to_http_request(); - let resp = test::block_on(index(req)).unwrap(); - assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST); + #[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(); + assert_eq!(resp.status(), http::StatusCode::OK); + } + + #[test] + fn test_index_not_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 index 9fea603..f922e05 100644 --- a/examples/testing/src/stream_response.rs +++ b/examples/testing/src/stream_response.rs @@ -1,50 +1,49 @@ // -// use bytes::Bytes; -// use futures::stream::poll_fn; -// use futures::{Async, Poll, Stream}; +use bytes::Bytes; +use futures::stream::poll_fn; +use futures::{Async, Poll}; -// use actix_web::http::{ContentEncoding, StatusCode}; -// use actix_web::test::TestServer; -// use actix_web::{Error, HttpRequest, HttpResponse}; +use actix_web::http::{ContentEncoding, StatusCode}; +use actix_web::{middleware::BodyEncoding, web, App, 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)))) -// }); +fn sse(_req: HttpRequest) -> HttpResponse { + let mut counter: usize = 5; -// HttpResponse::build(StatusCode::OK) -// .content_encoding(ContentEncoding::Identity) -// .content_type("text/event-stream") -// .streaming(server_events) -// } + // 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)))) + }); -// fn main() { -// // start new test server -// let mut srv = TestServer::new(|app| app.handler(sse)); + HttpResponse::build(StatusCode::OK) + .encoding(ContentEncoding::Identity) + .content_type("text/event-stream") + .streaming(server_events) +} -// // request stream -// let request = srv.get().finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); +pub fn main() { + App::new().route("/", web::get().to(sse)); +} -// // 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!(), -// } -// } -// } +#[cfg(test)] +mod tests { + use super::*; + use actix_web::{test, web, App}; + + #[test] + fn test_stream() { + let mut app = test::init_service(App::new().route("/", web::get().to(sse))); + 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" + ) + ); + } +} // diff --git a/examples/testing/src/websockets.rs b/examples/testing/src/websockets.rs deleted file mode 100644 index 8034116..0000000 --- a/examples/testing/src/websockets.rs +++ /dev/null @@ -1,30 +0,0 @@ -// -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()))); -} -//