1
0
mirror of https://github.com/actix/actix-website synced 2024-11-27 18:12:57 +01:00

Testing is done-ish.

This commit is contained in:
Cameron Dershem 2019-06-24 17:43:31 -04:00
parent 4f57ce2fdb
commit 60f4f116cf
8 changed files with 141 additions and 173 deletions

View File

@ -6,13 +6,13 @@ weight: 210
# Testing # 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. integration tests.
# Unit Tests # Unit Tests
For unit testing, actix provides a request builder type and a simple handler runner. For unit testing, actix-web provides a request builder type and a simple handler runner.
[*TestRequest*](../../actix-web/actix_web/test/struct.TestRequest.html) [*TestRequest*](https://docs.rs/actix-web/1.0.2/actix_web/test/struct.TestRequest.html)
implements a builder-like pattern. implements a builder-like pattern.
You can generate a `HttpRequest` instance with `to_http_request()`, or you can You can generate a `HttpRequest` instance with `to_http_request()`, or you can
run your handler with `block_on()`. run your handler with `block_on()`.
@ -21,49 +21,32 @@ run your handler with `block_on()`.
# Integration tests # Integration tests
There are several methods for testing your application. Actix provides There a few methods for testing your application. Actix-web can be used
[*TestServer*](../../actix-web/actix_web/test/struct.TestServer.html), which 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.
`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. methods can be used to send requests to the test server.
A simple form `TestServer` can be configured to use a handler. To create a `Service` for testing, use the `test::init_serivce` method which accepts a
`TestServer::new` method accepts a configuration function, and the only argument regular `App` builder.
for this function is a *test application* instance.
> 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. > for more information.
{{< include-example example="testing" file="integration_one.rs" section="integration-one" >}} {{< 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 If you need more complex application configuration testing should be very similar to creating
function the same way as you would for real http server configuration. 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" >}} {{< 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 # 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. If you need to test stream it would be enough to convert a
For example of testing [*Server Sent Events*](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events). [*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" >}} {{< 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" >}}

View File

@ -7,3 +7,5 @@ edition = "2018"
actix-web = "1.0" actix-web = "1.0"
futures = "0.1" futures = "0.1"
bytes = "0.4" bytes = "0.4"
serde = "1.0"
serde_json = "1.0"

View File

@ -1,22 +1,33 @@
use actix_web::{HttpRequest, Responder};
#[allow(dead_code)]
fn index(_req: HttpRequest) -> impl Responder {
"Hello world!"
}
// <integration-one> // <integration-one>
// use actix_web::test::TestServer; #[cfg(test)]
// use actix_web::HttpRequest; mod tests {
// use std::str; use super::*;
use actix_web::dev::Service;
use actix_web::{test, web, App};
// fn index(req: HttpRequest) -> &'static str { #[test]
// "Hello world!" 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() { assert!(resp.status().is_success());
// // start new test server }
// let mut srv = TestServer::new(|app| app.handler(index));
// let request = srv.get().finish().unwrap(); #[test]
// let response = srv.execute(request.send()).unwrap(); fn test_index_post() {
// assert!(response.status().is_success()); 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(); assert!(resp.status().is_client_error());
// let body = str::from_utf8(&bytes).unwrap(); }
// assert_eq!(body, "Hello world!"); }
// }
// </integration-one> // </integration-one>

View File

@ -1,21 +0,0 @@
// <integration-three>
// #[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<PParam>| format!("Welcome {}!", p.username)));
// });
// // now we can run our test code
// );
// </integration-three>

View File

@ -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<AppState>) -> impl Responder {
HttpResponse::Ok().json(data.get_ref())
}
// <integration-two> // <integration-two>
// 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 { #[test]
// HttpResponse::Ok().into() 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. assert!(resp.count == 4);
// 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());
// }
// </integration-two> // </integration-two>

View File

@ -1,10 +1,7 @@
mod integration_one; pub mod integration_one;
mod integration_three; pub mod integration_two;
mod integration_two; pub mod stream_response;
mod stream_response; use actix_web::{http, web, App, HttpRequest, HttpResponse};
mod websockets;
// <unit-tests>
use actix_web::{http, test, HttpRequest, HttpResponse};
fn index(req: HttpRequest) -> HttpResponse { 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) {
@ -16,14 +13,29 @@ fn index(req: HttpRequest) -> HttpResponse {
} }
fn main() { fn main() {
let req = App::new().route("/", web::get().to(index));
test::TestRequest::with_header("content-type", "text/plain").to_http_request(); }
// <unit-tests>
#[cfg(test)]
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(); let resp = test::block_on(index(req)).unwrap();
assert_eq!(resp.status(), http::StatusCode::OK); assert_eq!(resp.status(), http::StatusCode::OK);
}
#[test]
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 = test::block_on(index(req)).unwrap();
assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST); assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST);
}
} }
// </unit-tests> // </unit-tests>

View File

@ -1,50 +1,49 @@
// <stream-response> // <stream-response>
// use bytes::Bytes; use bytes::Bytes;
// use futures::stream::poll_fn; use futures::stream::poll_fn;
// use futures::{Async, Poll, Stream}; use futures::{Async, Poll};
// use actix_web::http::{ContentEncoding, StatusCode}; use actix_web::http::{ContentEncoding, StatusCode};
// use actix_web::test::TestServer; use actix_web::{middleware::BodyEncoding, web, App, Error, HttpRequest, HttpResponse};
// use actix_web::{Error, HttpRequest, HttpResponse};
// fn sse(_req: &HttpRequest) -> HttpResponse { fn sse(_req: HttpRequest) -> HttpResponse {
// let mut counter = 5usize; let mut counter: usize = 5;
// // yields `data: N` where N in [5; 1]
// let server_events = poll_fn(move || -> Poll<Option<Bytes>, 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) // yields `data: N` where N in [5; 1]
// .content_encoding(ContentEncoding::Identity) let server_events = poll_fn(move || -> Poll<Option<Bytes>, Error> {
// .content_type("text/event-stream") if counter == 0 {
// .streaming(server_events) return Ok(Async::Ready(None));
// } }
let payload = format!("data: {}\n\n", counter);
counter -= 1;
Ok(Async::Ready(Some(Bytes::from(payload))))
});
// fn main() { HttpResponse::build(StatusCode::OK)
// // start new test server .encoding(ContentEncoding::Identity)
// let mut srv = TestServer::new(|app| app.handler(sse)); .content_type("text/event-stream")
.streaming(server_events)
}
// // request stream pub fn main() {
// let request = srv.get().finish().unwrap(); App::new().route("/", web::get().to(sse));
// let response = srv.execute(request.send()).unwrap(); }
// assert!(response.status().is_success());
// // convert ClientResponse to future, start read body and wait first chunk #[cfg(test)]
// let mut stream = response.payload(); mod tests {
// loop { use super::*;
// match srv.execute(stream.into_future()) { use actix_web::{test, web, App};
// Ok((Some(bytes), remain)) => {
// println!("{:?}", bytes); #[test]
// stream = remain fn test_stream() {
// } let mut app = test::init_service(App::new().route("/", web::get().to(sse)));
// Ok((None, _)) => break, let req = test::TestRequest::get().to_request();
// Err(_) => panic!(), 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"
)
);
}
}
// </stream-response> // </stream-response>

View File

@ -1,30 +0,0 @@
// <web-socket>
use actix::{Actor, StreamHandler};
use actix_web::*;
use futures::Stream;
struct Ws; // <- WebSocket actor
impl Actor for Ws {
type Context = ws::WebsocketContext<Self>;
}
impl StreamHandler<ws::Message, ws::ProtocolError> 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())));
}
// </web-socket>