1
0
mirror of https://github.com/actix/actix-website synced 2025-02-25 13:32:49 +01:00

first pass at Testing.

This commit is contained in:
Cameron Dershem 2019-06-17 15:39:58 -04:00
parent ea1600c417
commit 083522ef19
9 changed files with 192 additions and 181 deletions

View File

@ -14,33 +14,10 @@ integration tests.
For unit testing, actix provides a request builder type and a simple handler runner. For unit testing, actix provides a request builder type and a simple handler runner.
[*TestRequest*](../../actix-web/actix_web/test/struct.TestRequest.html) [*TestRequest*](../../actix-web/actix_web/test/struct.TestRequest.html)
implements a builder-like pattern. implements a builder-like pattern.
You can generate a `HttpRequest` instance with `finish()`, or you can You can generate a `HttpRequest` instance with `to_http_request()`, or you can
run your handler with `run()` or `run_async()`. run your handler with `block_on()`.
```rust {{< include-example example="testing" file="main.rs" section="unit-tests" >}}
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);
}
```
# Integration 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) > Check the [api documentation](../../actix-web/actix_web/test/struct.TestApp.html)
> for more information. > for more information.
```rust {{< include-example example="testing" file="integration_one.rs" section="integration-one" >}}
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!");
}
```
The other option is to use an application factory. In this case, you need to pass the factory 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. function the same way as you would for real http server configuration.
```rust {{< include-example example="testing" file="integration_two.rs" section="integration-two" >}}
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());
}
```
If you need more complex application configuration, use the `TestServer::build_with_state()` 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 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, 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. and it runs when the actix system is configured. Thus, you can initialize any additional actors.
```rust {{< include-example example="testing" file="integration_three.rs" section="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
);
```
# 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 [*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). For example of testing [*Server Sent Events*](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events).
```rust {{< include-example example="testing" file="stream_response.rs" section="stream-response" >}}
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<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)
.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!(),
}
}
}
```
# WebSocket server tests # WebSocket server tests
@ -209,34 +66,4 @@ result of the future computation.
The following example demonstrates how to test a websocket handler: The following example demonstrates how to test a websocket handler:
```rust {{< include-example example="testing" file="websockets.rs" section="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())));
}
```

View File

@ -20,4 +20,5 @@ exclude = [
"errors", "errors",
"requests", "requests",
"responses", "responses",
"testing",
] ]

View File

@ -0,0 +1,10 @@
[package]
name = "testing"
version = "0.1.0"
authors = ["Cameron Dershem <cameron@pinkhatbeard.com>"]
edition = "2018"
[dependencies]
actix-web = "1.0"
futures = "0.1"
bytes = "0.4"

View File

@ -0,0 +1,22 @@
// <integration-one>
// 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!");
// }
// </integration-one>

View File

@ -0,0 +1,21 @@
// <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

@ -0,0 +1,21 @@
// <integration-two>
// 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());
// }
// </integration-two>

View File

@ -0,0 +1,29 @@
mod integration_one;
mod integration_three;
mod integration_two;
mod stream_response;
mod websockets;
// <unit-tests>
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);
}
// </unit-tests>

View File

@ -0,0 +1,50 @@
// <stream-response>
// 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<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)
// .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!(),
// }
// }
// }
// </stream-response>

View File

@ -0,0 +1,30 @@
// <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>