mirror of
https://github.com/fafhrd91/actix-web
synced 2024-11-24 08:22:59 +01:00
add unit test helper
This commit is contained in:
parent
7f77ba557d
commit
743235b8fd
@ -3,6 +3,43 @@
|
||||
Every application should be well tested and. Actix provides the tools to perform unit and
|
||||
integration tests.
|
||||
|
||||
## Unit tests
|
||||
|
||||
For unit testing actix provides request builder type and simple handler runner.
|
||||
[*TestRequest*](../actix_web/test/struct.TestRequest.html) implements builder-like pattern.
|
||||
You can generate `HttpRequest` instance with `finish()` method or you can
|
||||
run your handler with `run()` or `run_async()` methods.
|
||||
|
||||
```rust
|
||||
# extern crate http;
|
||||
# extern crate actix_web;
|
||||
use http::{header, StatusCode};
|
||||
use actix_web::*;
|
||||
use actix_web::test::TestRequest;
|
||||
|
||||
fn index(req: HttpRequest) -> HttpResponse {
|
||||
if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) {
|
||||
if let Ok(s) = hdr.to_str() {
|
||||
return httpcodes::HTTPOk.response()
|
||||
}
|
||||
}
|
||||
httpcodes::HTTPBadRequest.response()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let resp = TestRequest::with_header("content-type", "text/plain")
|
||||
.run(index)
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let resp = TestRequest::default()
|
||||
.run(index)
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Integration tests
|
||||
|
||||
There are several methods how you can test your application. Actix provides
|
||||
|
@ -313,6 +313,7 @@ mod tests {
|
||||
use std::str::FromStr;
|
||||
use http::{Method, Version, Uri, HeaderMap, StatusCode};
|
||||
use super::*;
|
||||
use test::TestRequest;
|
||||
use httprequest::HttpRequest;
|
||||
use httpcodes;
|
||||
|
||||
@ -322,9 +323,7 @@ mod tests {
|
||||
.resource("/test", |r| r.h(httpcodes::HTTPOk))
|
||||
.finish();
|
||||
|
||||
let req = HttpRequest::new(
|
||||
Method::GET, Uri::from_str("/test").unwrap(),
|
||||
Version::HTTP_11, HeaderMap::new(), None);
|
||||
let req = TestRequest::with_uri("/test").finish();
|
||||
let resp = app.run(req);
|
||||
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||
|
||||
|
@ -412,6 +412,7 @@ impl<S> Handler<S> for NormalizePath {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use http::{header, Method};
|
||||
use test::TestRequest;
|
||||
use application::Application;
|
||||
|
||||
fn index(_req: HttpRequest) -> HttpResponse {
|
||||
@ -438,7 +439,7 @@ mod tests {
|
||||
("/resource2/?p1=1&p2=2", "", StatusCode::OK)
|
||||
];
|
||||
for (path, target, code) in params {
|
||||
let req = app.prepare_request(HttpRequest::from_path(path));
|
||||
let req = app.prepare_request(TestRequest::with_uri(path).finish());
|
||||
let resp = app.run(req);
|
||||
let r = resp.as_response().unwrap();
|
||||
assert_eq!(r.status(), code);
|
||||
@ -470,7 +471,7 @@ mod tests {
|
||||
("/resource2/?p1=1&p2=2", StatusCode::OK)
|
||||
];
|
||||
for (path, code) in params {
|
||||
let req = app.prepare_request(HttpRequest::from_path(path));
|
||||
let req = app.prepare_request(TestRequest::with_uri(path).finish());
|
||||
let resp = app.run(req);
|
||||
let r = resp.as_response().unwrap();
|
||||
assert_eq!(r.status(), code);
|
||||
@ -501,7 +502,7 @@ mod tests {
|
||||
("/////resource1/a//b/?p=1", "", StatusCode::NOT_FOUND),
|
||||
];
|
||||
for (path, target, code) in params {
|
||||
let req = app.prepare_request(HttpRequest::from_path(path));
|
||||
let req = app.prepare_request(TestRequest::with_uri(path).finish());
|
||||
let resp = app.run(req);
|
||||
let r = resp.as_response().unwrap();
|
||||
assert_eq!(r.status(), code);
|
||||
@ -558,7 +559,7 @@ mod tests {
|
||||
("/////resource2/a///b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
|
||||
];
|
||||
for (path, target, code) in params {
|
||||
let req = app.prepare_request(HttpRequest::from_path(path));
|
||||
let req = app.prepare_request(TestRequest::with_uri(path).finish());
|
||||
let resp = app.run(req);
|
||||
let r = resp.as_response().unwrap();
|
||||
assert_eq!(r.status(), code);
|
||||
|
@ -119,31 +119,6 @@ impl HttpRequest<()> {
|
||||
HttpRequest(msg, None, None)
|
||||
}
|
||||
|
||||
/// Construct a new Request.
|
||||
#[inline]
|
||||
#[cfg(test)]
|
||||
pub fn from_path(path: &str) -> HttpRequest
|
||||
{
|
||||
use std::str::FromStr;
|
||||
|
||||
HttpRequest(
|
||||
SharedHttpMessage::from_message(HttpMessage {
|
||||
method: Method::GET,
|
||||
uri: Uri::from_str(path).unwrap(),
|
||||
version: Version::HTTP_11,
|
||||
headers: HeaderMap::new(),
|
||||
params: Params::default(),
|
||||
cookies: None,
|
||||
addr: None,
|
||||
payload: None,
|
||||
extensions: Extensions::new(),
|
||||
info: None,
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Construct new http request with state.
|
||||
pub fn with_state<S>(self, state: Rc<S>, router: Router) -> HttpRequest<S> {
|
||||
@ -163,7 +138,7 @@ impl<S> HttpRequest<S> {
|
||||
// mutable reference should not be returned as result for request's method
|
||||
#[inline(always)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
|
||||
fn as_mut(&self) -> &mut HttpMessage {
|
||||
pub(crate) fn as_mut(&self) -> &mut HttpMessage {
|
||||
self.0.get_mut()
|
||||
}
|
||||
|
||||
@ -657,30 +632,25 @@ mod tests {
|
||||
use std::str::FromStr;
|
||||
use router::Pattern;
|
||||
use resource::Resource;
|
||||
use test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let req = HttpRequest::new(
|
||||
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None);
|
||||
let req = TestRequest::with_header("content-type", "text/plain").finish();
|
||||
let dbg = format!("{:?}", req);
|
||||
assert!(dbg.contains("HttpRequest"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_request_cookies() {
|
||||
let req = HttpRequest::new(
|
||||
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None);
|
||||
let req = HttpRequest::default();
|
||||
assert!(req.cookies().unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_cookies() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(header::COOKIE,
|
||||
header::HeaderValue::from_static("cookie1=value1; cookie2=value2"));
|
||||
|
||||
let req = HttpRequest::new(
|
||||
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None);
|
||||
let req = TestRequest::with_header(
|
||||
header::COOKIE, "cookie1=value1; cookie2=value2").finish();
|
||||
{
|
||||
let cookies = req.cookies().unwrap();
|
||||
assert_eq!(cookies.len(), 2);
|
||||
@ -733,8 +703,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_match_info() {
|
||||
let mut req = HttpRequest::new(Method::GET, Uri::from_str("/value/?id=test").unwrap(),
|
||||
Version::HTTP_11, HeaderMap::new(), None);
|
||||
let mut req = TestRequest::with_uri("/value/?id=test").finish();
|
||||
|
||||
let mut resource = Resource::<()>::default();
|
||||
resource.name("index");
|
||||
@ -748,15 +717,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_chunked() {
|
||||
let req = HttpRequest::new(
|
||||
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, HeaderMap::new(), None);
|
||||
let req = HttpRequest::default();
|
||||
assert!(!req.chunked().unwrap());
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(header::TRANSFER_ENCODING,
|
||||
header::HeaderValue::from_static("chunked"));
|
||||
let req = HttpRequest::new(
|
||||
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None);
|
||||
let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
|
||||
assert!(req.chunked().unwrap());
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
|
202
src/test/mod.rs
202
src/test/mod.rs
@ -1,17 +1,30 @@
|
||||
//! Various helpers for Actix applications to use during testing.
|
||||
|
||||
use std::{net, thread};
|
||||
use std::rc::Rc;
|
||||
use std::sync::mpsc;
|
||||
use std::str::FromStr;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use actix::{Arbiter, SyncAddress, System, msgs};
|
||||
use cookie::Cookie;
|
||||
use http::{Uri, Method, Version, HeaderMap, HttpTryFrom};
|
||||
use http::header::{HeaderName, HeaderValue};
|
||||
use futures::Future;
|
||||
use tokio_core::net::TcpListener;
|
||||
use tokio_core::reactor::Core;
|
||||
|
||||
use error::Error;
|
||||
use server::HttpServer;
|
||||
use handler::Handler;
|
||||
use handler::{Handler, Responder, ReplyItem};
|
||||
use channel::{HttpHandler, IntoHttpHandler};
|
||||
use middlewares::Middleware;
|
||||
use application::{Application, HttpApplication};
|
||||
|
||||
use param::Params;
|
||||
use router::Router;
|
||||
use payload::Payload;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
|
||||
/// The `TestServer` type.
|
||||
///
|
||||
@ -192,3 +205,188 @@ impl<S: 'static> Iterator for TestApp<S> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test `HttpRequest` builder
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate http;
|
||||
/// # extern crate actix_web;
|
||||
/// # use http::{header, StatusCode};
|
||||
/// # use actix_web::*;
|
||||
/// use actix_web::test::TestRequest;
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> HttpResponse {
|
||||
/// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) {
|
||||
/// httpcodes::HTTPOk.response()
|
||||
/// } else {
|
||||
/// httpcodes::HTTPBadRequest.response()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let resp = TestRequest::with_header("content-type", "text/plain")
|
||||
/// .run(index).unwrap();
|
||||
/// assert_eq!(resp.status(), StatusCode::OK);
|
||||
///
|
||||
/// let resp = TestRequest::default()
|
||||
/// .run(index).unwrap();
|
||||
/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
/// }
|
||||
/// ```
|
||||
pub struct TestRequest<S> {
|
||||
state: S,
|
||||
version: Version,
|
||||
method: Method,
|
||||
uri: Uri,
|
||||
headers: HeaderMap,
|
||||
params: Params<'static>,
|
||||
cookies: Option<Vec<Cookie<'static>>>,
|
||||
payload: Option<Payload>,
|
||||
}
|
||||
|
||||
impl Default for TestRequest<()> {
|
||||
|
||||
fn default() -> TestRequest<()> {
|
||||
TestRequest {
|
||||
state: (),
|
||||
method: Method::GET,
|
||||
uri: Uri::from_str("/").unwrap(),
|
||||
version: Version::HTTP_11,
|
||||
headers: HeaderMap::new(),
|
||||
params: Params::default(),
|
||||
cookies: None,
|
||||
payload: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestRequest<()> {
|
||||
|
||||
/// Create TestReqeust and set request uri
|
||||
pub fn with_uri(path: &str) -> TestRequest<()> {
|
||||
TestRequest::default().uri(path)
|
||||
}
|
||||
|
||||
/// Create TestReqeust and set header
|
||||
pub fn with_header<K, V>(key: K, value: V) -> TestRequest<()>
|
||||
where HeaderName: HttpTryFrom<K>,
|
||||
HeaderValue: HttpTryFrom<V>
|
||||
{
|
||||
TestRequest::default().header(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> TestRequest<S> {
|
||||
|
||||
/// Start HttpRequest build process with application state
|
||||
pub fn with_state(state: S) -> TestRequest<S> {
|
||||
TestRequest {
|
||||
state: state,
|
||||
method: Method::GET,
|
||||
uri: Uri::from_str("/").unwrap(),
|
||||
version: Version::HTTP_11,
|
||||
headers: HeaderMap::new(),
|
||||
params: Params::default(),
|
||||
cookies: None,
|
||||
payload: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set HTTP version of this request
|
||||
pub fn version(mut self, ver: Version) -> Self {
|
||||
self.version = ver;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set HTTP method of this request
|
||||
pub fn method(mut self, meth: Method) -> Self {
|
||||
self.method = meth;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set HTTP Uri of this request
|
||||
pub fn uri(mut self, path: &str) -> Self {
|
||||
self.uri = Uri::from_str(path).unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a header
|
||||
pub fn header<K, V>(mut self, key: K, value: V) -> Self
|
||||
where HeaderName: HttpTryFrom<K>,
|
||||
HeaderValue: HttpTryFrom<V>
|
||||
{
|
||||
if let Ok(key) = HeaderName::try_from(key) {
|
||||
if let Ok(value) = HeaderValue::try_from(value) {
|
||||
self.headers.append(key, value);
|
||||
return self
|
||||
}
|
||||
}
|
||||
panic!("Can not create header");
|
||||
}
|
||||
|
||||
/// Set request path pattern parameter
|
||||
pub fn param(mut self, name: &'static str, value: &'static str) -> Self {
|
||||
self.params.add(name, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Complete request creation and generate `HttpRequest` instance
|
||||
pub fn finish(self) -> HttpRequest<S> {
|
||||
let TestRequest { state, method, uri, version, headers, params, cookies, payload } = self;
|
||||
let req = HttpRequest::new(method, uri, version, headers, payload);
|
||||
req.as_mut().cookies = cookies;
|
||||
req.as_mut().params = params;
|
||||
let (router, _) = Router::new::<S>("/", HashMap::new());
|
||||
req.with_state(Rc::new(state), router)
|
||||
}
|
||||
|
||||
/// This method generates `HttpRequest` instance and runs handler
|
||||
/// with generated request.
|
||||
///
|
||||
/// This method panics is handler returns actor or async result.
|
||||
pub fn run<H: Handler<S>>(self, mut h: H) ->
|
||||
Result<HttpResponse, <<H as Handler<S>>::Result as Responder>::Error>
|
||||
{
|
||||
let req = self.finish();
|
||||
let resp = h.handle(req.clone());
|
||||
|
||||
match resp.respond_to(req.clone_without_state()) {
|
||||
Ok(resp) => {
|
||||
match resp.into().into() {
|
||||
ReplyItem::Message(resp) => Ok(resp),
|
||||
ReplyItem::Actor(_) => panic!("Actor handler is not supported."),
|
||||
ReplyItem::Future(_) => panic!("Async handler is not supported."),
|
||||
}
|
||||
},
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
/// This method generates `HttpRequest` instance and runs handler
|
||||
/// with generated request.
|
||||
///
|
||||
/// This method panics is handler returns actor.
|
||||
pub fn run_async<H, R, F, E>(self, h: H) -> Result<HttpResponse, E>
|
||||
where H: Fn(HttpRequest<S>) -> F + 'static,
|
||||
F: Future<Item=R, Error=E> + 'static,
|
||||
R: Responder<Error=E> + 'static,
|
||||
E: Into<Error> + 'static
|
||||
{
|
||||
let req = self.finish();
|
||||
let fut = h(req.clone());
|
||||
|
||||
let mut core = Core::new().unwrap();
|
||||
match core.run(fut) {
|
||||
Ok(r) => {
|
||||
match r.respond_to(req.clone_without_state()) {
|
||||
Ok(reply) => match reply.into().into() {
|
||||
ReplyItem::Message(resp) => Ok(resp),
|
||||
_ => panic!("Nested async replies are not supported"),
|
||||
},
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
},
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user