1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-27 17:52:56 +01:00

add test::read_response; fix TestRequest::app_data()

This commit is contained in:
Nikolay Kim 2019-04-14 19:52:12 -07:00
parent 4cc2b38059
commit f9078d41cd
5 changed files with 213 additions and 71 deletions

View File

@ -1,5 +1,17 @@
# Changes # Changes
## [1.0.0-alpha.7] - 2019-04-xx
### Added
* Added helper functions for reading test response body,
`test::read_response()` and test::read_response_json()`
### Fixed
* Fixed `TestRequest::app_data()`
## [1.0.0-alpha.6] - 2019-04-14 ## [1.0.0-alpha.6] - 2019-04-14
### Changed ### Changed

View File

@ -306,9 +306,7 @@ impl WebsocketsRequest {
} }
} else { } else {
log::trace!("Invalid connection header: {:?}", conn); log::trace!("Invalid connection header: {:?}", conn);
return Err(WsClientError::InvalidConnectionHeader( return Err(WsClientError::InvalidConnectionHeader(conn.clone()));
conn.clone(),
));
} }
} else { } else {
log::trace!("Missing connection header"); log::trace!("Missing connection header");

View File

@ -61,6 +61,7 @@ pub(crate) trait DataFactoryResult {
/// web::get().to(index))); /// web::get().to(index)));
/// } /// }
/// ``` /// ```
#[derive(Debug)]
pub struct Data<T>(Arc<T>); pub struct Data<T>(Arc<T>);
impl<T> Data<T> { impl<T> Data<T> {

View File

@ -4,7 +4,7 @@ use std::rc::Rc;
use actix_http::cookie::Cookie; use actix_http::cookie::Cookie;
use actix_http::http::header::{Header, HeaderName, IntoHeaderValue}; use actix_http::http::header::{Header, HeaderName, IntoHeaderValue};
use actix_http::http::{HttpTryFrom, Method, StatusCode, Version}; use actix_http::http::{HttpTryFrom, Method, StatusCode, Uri, Version};
use actix_http::test::TestRequest as HttpTestRequest; use actix_http::test::TestRequest as HttpTestRequest;
use actix_http::{Extensions, Request}; use actix_http::{Extensions, Request};
use actix_router::{Path, ResourceDef, Url}; use actix_router::{Path, ResourceDef, Url};
@ -12,14 +12,17 @@ use actix_rt::Runtime;
use actix_server_config::ServerConfig; use actix_server_config::ServerConfig;
use actix_service::{FnService, IntoNewService, NewService, Service}; use actix_service::{FnService, IntoNewService, NewService, Service};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::{future::{lazy, ok, Future}, stream::Stream}; use futures::{
future::{lazy, ok, Future},
stream::Stream,
};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde_json; use serde_json;
pub use actix_http::test::TestBuffer; pub use actix_http::test::TestBuffer;
use crate::config::{AppConfig, AppConfigInner}; use crate::config::{AppConfig, AppConfigInner};
use crate::data::RouteData; use crate::data::{Data, RouteData};
use crate::dev::{Body, MessageBody, Payload}; use crate::dev::{Body, MessageBody, Payload};
use crate::request::HttpRequestPool; use crate::request::HttpRequestPool;
use crate::rmap::ResourceMap; use crate::rmap::ResourceMap;
@ -81,11 +84,12 @@ pub fn default_service(
/// This method accepts application builder instance, and constructs /// This method accepts application builder instance, and constructs
/// service. /// service.
/// ///
/// ```rust,ignore /// ```rust
/// use actix_service::Service; /// use actix_service::Service;
/// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; /// use actix_web::{test, web, App, HttpResponse, http::StatusCode};
/// ///
/// fn main() { /// #[test]
/// fn test_init_service() {
/// let mut app = test::init_service( /// let mut app = test::init_service(
/// App::new() /// App::new()
/// .service(web::resource("/test").to(|| HttpResponse::Ok())) /// .service(web::resource("/test").to(|| HttpResponse::Ok()))
@ -118,11 +122,12 @@ where
/// Calls service and waits for response future completion. /// Calls service and waits for response future completion.
/// ///
/// ```rust,ignore /// ```rust
/// use actix_web::{test, App, HttpResponse, http::StatusCode}; /// use actix_web::{test, App, HttpResponse, http::StatusCode};
/// use actix_service::Service; /// use actix_service::Service;
/// ///
/// fn main() { /// #[test]
/// fn test_response() {
/// let mut app = test::init_service( /// let mut app = test::init_service(
/// App::new() /// App::new()
/// .service(web::resource("/test").to(|| HttpResponse::Ok())) /// .service(web::resource("/test").to(|| HttpResponse::Ok()))
@ -144,6 +149,101 @@ where
block_on(app.call(req)).unwrap() block_on(app.call(req)).unwrap()
} }
/// Helper function that returns a response body of a TestRequest
/// This function blocks the current thread until futures complete.
///
/// ```rust
/// use actix_web::{test, web, App, HttpResponse, http::header};
/// use bytes::Bytes;
///
/// #[test]
/// fn test_index() {
/// let mut app = test::init_service(
/// App::new().service(
/// web::resource("/index.html")
/// .route(web::post().to(
/// || HttpResponse::Ok().body("welcome!")))));
///
/// let req = test::TestRequest::post()
/// .uri("/index.html")
/// .header(header::CONTENT_TYPE, "application/json")
/// .to_request();
///
/// let result = test::read_response(&mut app, req);
/// assert_eq!(result, Bytes::from_static(b"welcome!"));
/// }
/// ```
pub fn read_response<S, B>(app: &mut S, req: Request) -> Bytes
where
S: Service<Request = Request, Response = ServiceResponse<B>, Error = Error>,
B: MessageBody,
{
block_on(app.call(req).and_then(|mut resp: ServiceResponse<B>| {
resp.take_body()
.fold(BytesMut::new(), move |mut body, chunk| {
body.extend_from_slice(&chunk);
Ok::<_, Error>(body)
})
.map(|body: BytesMut| body.freeze())
}))
.unwrap_or_else(|_| panic!("read_response failed at block_on unwrap"))
}
/// Helper function that returns a deserialized response body of a TestRequest
/// This function blocks the current thread until futures complete.
///
/// ```rust
/// use actix_web::{App, test, web, HttpResponse, http::header};
/// use serde::{Serialize, Deserialize};
///
/// #[derive(Serialize, Deserialize)]
/// pub struct Person {
/// id: String,
/// name: String
/// }
///
/// #[test]
/// fn test_add_person() {
/// let mut app = test::init_service(
/// App::new().service(
/// web::resource("/people")
/// .route(web::post().to(|person: web::Json<Person>| {
/// HttpResponse::Ok()
/// .json(person.into_inner())})
/// )));
///
/// let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
///
/// let req = test::TestRequest::post()
/// .uri("/people")
/// .header(header::CONTENT_TYPE, "application/json")
/// .set_payload(payload)
/// .to_request();
///
/// let result: Person = test::read_response_json(&mut app, req);
/// }
/// ```
pub fn read_response_json<S, B, T>(app: &mut S, req: Request) -> T
where
S: Service<Request = Request, Response = ServiceResponse<B>, Error = Error>,
B: MessageBody,
T: DeserializeOwned,
{
block_on(app.call(req).and_then(|mut resp: ServiceResponse<B>| {
resp.take_body()
.fold(BytesMut::new(), move |mut body, chunk| {
body.extend_from_slice(&chunk);
Ok::<_, Error>(body)
})
.and_then(|body: BytesMut| {
ok(serde_json::from_slice(&body).unwrap_or_else(|_| {
panic!("read_response_json failed during deserialization")
}))
})
}))
.unwrap_or_else(|_| panic!("read_response_json failed at block_on unwrap"))
}
/// Test `Request` builder. /// Test `Request` builder.
/// ///
/// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern. /// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern.
@ -153,7 +253,7 @@ where
/// * `TestRequest::to_from` creates `ServiceFromRequest` instance, which is used for testing extractors. /// * `TestRequest::to_from` creates `ServiceFromRequest` instance, which is used for testing extractors.
/// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers. /// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers.
/// ///
/// ```rust,ignore /// ```rust
/// # use futures::IntoFuture; /// # use futures::IntoFuture;
/// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; /// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage};
/// use actix_web::http::{header, StatusCode}; /// use actix_web::http::{header, StatusCode};
@ -166,7 +266,8 @@ where
/// } /// }
/// } /// }
/// ///
/// fn main() { /// #[test]
/// fn test_index() {
/// let req = test::TestRequest::with_header("content-type", "text/plain") /// let req = test::TestRequest::with_header("content-type", "text/plain")
/// .to_http_request(); /// .to_http_request();
/// ///
@ -183,6 +284,7 @@ pub struct TestRequest {
rmap: ResourceMap, rmap: ResourceMap,
config: AppConfigInner, config: AppConfigInner,
route_data: Extensions, route_data: Extensions,
path: Path<Url>,
} }
impl Default for TestRequest { impl Default for TestRequest {
@ -192,6 +294,7 @@ impl Default for TestRequest {
rmap: ResourceMap::new(ResourceDef::new("")), rmap: ResourceMap::new(ResourceDef::new("")),
config: AppConfigInner::default(), config: AppConfigInner::default(),
route_data: Extensions::new(), route_data: Extensions::new(),
path: Path::new(Url::new(Uri::default())),
} }
} }
} }
@ -267,6 +370,12 @@ impl TestRequest {
self self
} }
/// Set request path pattern parameter
pub fn param(mut self, name: &'static str, value: &'static str) -> Self {
self.path.add_static(name, value);
self
}
/// Set request payload /// Set request payload
pub fn set_payload<B: Into<Bytes>>(mut self, data: B) -> Self { pub fn set_payload<B: Into<Bytes>>(mut self, data: B) -> Self {
self.req.set_payload(data); self.req.set_payload(data);
@ -276,7 +385,7 @@ impl TestRequest {
/// Set application data. This is equivalent of `App::data()` method /// Set application data. This is equivalent of `App::data()` method
/// for testing purpose. /// for testing purpose.
pub fn app_data<T: 'static>(self, data: T) -> Self { pub fn app_data<T: 'static>(self, data: T) -> Self {
self.config.extensions.borrow_mut().insert(data); self.config.extensions.borrow_mut().insert(Data::new(data));
self self
} }
@ -302,9 +411,10 @@ impl TestRequest {
/// Complete request creation and generate `ServiceRequest` instance /// Complete request creation and generate `ServiceRequest` instance
pub fn to_srv_request(mut self) -> ServiceRequest { pub fn to_srv_request(mut self) -> ServiceRequest {
let (head, payload) = self.req.finish().into_parts(); let (head, payload) = self.req.finish().into_parts();
self.path.get_mut().update(&head.uri);
let req = HttpRequest::new( let req = HttpRequest::new(
Path::new(Url::new(head.uri.clone())), self.path,
head, head,
Rc::new(self.rmap), Rc::new(self.rmap),
AppConfig::new(self.config), AppConfig::new(self.config),
@ -322,9 +432,10 @@ impl TestRequest {
/// Complete request creation and generate `HttpRequest` instance /// Complete request creation and generate `HttpRequest` instance
pub fn to_http_request(mut self) -> HttpRequest { pub fn to_http_request(mut self) -> HttpRequest {
let (head, _) = self.req.finish().into_parts(); let (head, _) = self.req.finish().into_parts();
self.path.get_mut().update(&head.uri);
let mut req = HttpRequest::new( let mut req = HttpRequest::new(
Path::new(Url::new(head.uri.clone())), self.path,
head, head,
Rc::new(self.rmap), Rc::new(self.rmap),
AppConfig::new(self.config), AppConfig::new(self.config),
@ -337,9 +448,10 @@ impl TestRequest {
/// Complete request creation and generate `HttpRequest` and `Payload` instances /// Complete request creation and generate `HttpRequest` and `Payload` instances
pub fn to_http_parts(mut self) -> (HttpRequest, Payload) { pub fn to_http_parts(mut self) -> (HttpRequest, Payload) {
let (head, payload) = self.req.finish().into_parts(); let (head, payload) = self.req.finish().into_parts();
self.path.get_mut().update(&head.uri);
let mut req = HttpRequest::new( let mut req = HttpRequest::new(
Path::new(Url::new(head.uri.clone())), self.path,
head, head,
Rc::new(self.rmap), Rc::new(self.rmap),
AppConfig::new(self.config), AppConfig::new(self.config),
@ -365,55 +477,74 @@ impl TestRequest {
{ {
block_on(f) block_on(f)
} }
}
/// Helper function that returns a deserialized response body of a TestRequest #[cfg(test)]
/// This function blocks the current thread until futures complete. mod tests {
/// use serde::{Deserialize, Serialize};
/// ```rust use std::time::SystemTime;
/// use actix_web::{App, test, web, HttpResponse, http::header};
/// use serde::{Serialize, Deserialize}; use super::*;
/// use crate::{http::header, web, App, HttpResponse};
/// #[derive(Serialize, Deserialize)]
/// pub struct Person { id: String, name: String } #[test]
/// fn test_basics() {
/// #[test] let req = TestRequest::with_hdr(header::ContentType::json())
/// fn test_add_person() { .version(Version::HTTP_2)
/// let mut app = test::init_service(App::new().service( .set(header::Date(SystemTime::now().into()))
/// web::resource("/people") .param("test", "123")
/// .route(web::post().to(|person: web::Json<Person>| { .app_data(10u32)
/// HttpResponse::Ok() .to_http_request();
/// .json(person.into_inner())}) assert!(req.headers().contains_key(header::CONTENT_TYPE));
/// ))); assert!(req.headers().contains_key(header::DATE));
/// assert_eq!(&req.match_info()["test"], "123");
/// let payload = r#"{"id":"12345","name":"Nikolay Kim"}"#.as_bytes(); assert_eq!(req.version(), Version::HTTP_2);
/// let data = req.app_data::<u32>().unwrap();
/// let req = test::TestRequest::post() assert_eq!(*data, 10);
/// .uri("/people") assert_eq!(*data.get_ref(), 10);
/// .header(header::CONTENT_TYPE, "application/json") }
/// .set_payload(payload)
/// .to_request(); #[test]
/// fn test_response() {
/// let result: Person = test::read_response_json(&mut app, req); let mut app = init_service(
/// } App::new().service(
/// ``` web::resource("/index.html")
pub fn read_response_json<S, B, T>(app: &mut S, req: Request) -> T .route(web::post().to(|| HttpResponse::Ok().body("welcome!"))),
where ),
S: Service<Request = Request, Response = ServiceResponse<B>, Error = Error>, );
B: MessageBody,
T: DeserializeOwned, let req = TestRequest::post()
{ .uri("/index.html")
block_on(app.call(req).and_then(|mut resp: ServiceResponse<B>| { .header(header::CONTENT_TYPE, "application/json")
resp.take_body() .to_request();
.fold(BytesMut::new(), move |mut body, chunk| {
body.extend_from_slice(&chunk); let result = read_response(&mut app, req);
Ok::<_, Error>(body) assert_eq!(result, Bytes::from_static(b"welcome!"));
}) }
.and_then(|body: BytesMut| {
ok(serde_json::from_slice(&body).unwrap_or_else(|_| { #[derive(Serialize, Deserialize)]
panic!("read_response_json failed during deserialization") pub struct Person {
})) id: String,
}) name: String,
})) }
.unwrap_or_else(|_| panic!("read_response_json failed at block_on unwrap"))
#[test]
fn test_response_json() {
let mut app = init_service(App::new().service(web::resource("/people").route(
web::post().to(|person: web::Json<Person>| {
HttpResponse::Ok().json(person.into_inner())
}),
)));
let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
let req = TestRequest::post()
.uri("/people")
.header(header::CONTENT_TYPE, "application/json")
.set_payload(payload)
.to_request();
let result: Person = read_response_json(&mut app, req);
assert_eq!(&result.id, "12345");
} }
} }

View File

@ -103,7 +103,7 @@ pub fn route() -> Route {
/// * /{project_id} /// * /{project_id}
/// ///
pub fn get() -> Route { pub fn get() -> Route {
Route::new().method(Method::GET) method(Method::GET)
} }
/// Create *route* with `POST` method guard. /// Create *route* with `POST` method guard.
@ -123,7 +123,7 @@ pub fn get() -> Route {
/// * /{project_id} /// * /{project_id}
/// ///
pub fn post() -> Route { pub fn post() -> Route {
Route::new().method(Method::POST) method(Method::POST)
} }
/// Create *route* with `PUT` method guard. /// Create *route* with `PUT` method guard.
@ -143,7 +143,7 @@ pub fn post() -> Route {
/// * /{project_id} /// * /{project_id}
/// ///
pub fn put() -> Route { pub fn put() -> Route {
Route::new().method(Method::PUT) method(Method::PUT)
} }
/// Create *route* with `PATCH` method guard. /// Create *route* with `PATCH` method guard.
@ -163,7 +163,7 @@ pub fn put() -> Route {
/// * /{project_id} /// * /{project_id}
/// ///
pub fn patch() -> Route { pub fn patch() -> Route {
Route::new().method(Method::PATCH) method(Method::PATCH)
} }
/// Create *route* with `DELETE` method guard. /// Create *route* with `DELETE` method guard.
@ -183,7 +183,7 @@ pub fn patch() -> Route {
/// * /{project_id} /// * /{project_id}
/// ///
pub fn delete() -> Route { pub fn delete() -> Route {
Route::new().method(Method::DELETE) method(Method::DELETE)
} }
/// Create *route* with `HEAD` method guard. /// Create *route* with `HEAD` method guard.
@ -203,7 +203,7 @@ pub fn delete() -> Route {
/// * /{project_id} /// * /{project_id}
/// ///
pub fn head() -> Route { pub fn head() -> Route {
Route::new().method(Method::HEAD) method(Method::HEAD)
} }
/// Create *route* and add method guard. /// Create *route* and add method guard.