1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-06-10 17:29:36 +00: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
## [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
### Changed

View file

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

View file

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

View file

@ -4,7 +4,7 @@ use std::rc::Rc;
use actix_http::cookie::Cookie;
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::{Extensions, Request};
use actix_router::{Path, ResourceDef, Url};
@ -12,14 +12,17 @@ use actix_rt::Runtime;
use actix_server_config::ServerConfig;
use actix_service::{FnService, IntoNewService, NewService, Service};
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_json;
pub use actix_http::test::TestBuffer;
use crate::config::{AppConfig, AppConfigInner};
use crate::data::RouteData;
use crate::data::{Data, RouteData};
use crate::dev::{Body, MessageBody, Payload};
use crate::request::HttpRequestPool;
use crate::rmap::ResourceMap;
@ -81,11 +84,12 @@ pub fn default_service(
/// This method accepts application builder instance, and constructs
/// service.
///
/// ```rust,ignore
/// ```rust
/// use actix_service::Service;
/// use actix_web::{test, web, App, HttpResponse, http::StatusCode};
///
/// fn main() {
/// #[test]
/// fn test_init_service() {
/// let mut app = test::init_service(
/// App::new()
/// .service(web::resource("/test").to(|| HttpResponse::Ok()))
@ -118,11 +122,12 @@ where
/// Calls service and waits for response future completion.
///
/// ```rust,ignore
/// ```rust
/// use actix_web::{test, App, HttpResponse, http::StatusCode};
/// use actix_service::Service;
///
/// fn main() {
/// #[test]
/// fn test_response() {
/// let mut app = test::init_service(
/// App::new()
/// .service(web::resource("/test").to(|| HttpResponse::Ok()))
@ -144,6 +149,101 @@ where
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.
///
/// 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_http_request` creates `HttpRequest` instance, which is used for testing handlers.
///
/// ```rust,ignore
/// ```rust
/// # use futures::IntoFuture;
/// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage};
/// 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")
/// .to_http_request();
///
@ -183,6 +284,7 @@ pub struct TestRequest {
rmap: ResourceMap,
config: AppConfigInner,
route_data: Extensions,
path: Path<Url>,
}
impl Default for TestRequest {
@ -192,6 +294,7 @@ impl Default for TestRequest {
rmap: ResourceMap::new(ResourceDef::new("")),
config: AppConfigInner::default(),
route_data: Extensions::new(),
path: Path::new(Url::new(Uri::default())),
}
}
}
@ -267,6 +370,12 @@ impl TestRequest {
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
pub fn set_payload<B: Into<Bytes>>(mut self, data: B) -> Self {
self.req.set_payload(data);
@ -276,7 +385,7 @@ impl TestRequest {
/// Set application data. This is equivalent of `App::data()` method
/// for testing purpose.
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
}
@ -302,9 +411,10 @@ impl TestRequest {
/// Complete request creation and generate `ServiceRequest` instance
pub fn to_srv_request(mut self) -> ServiceRequest {
let (head, payload) = self.req.finish().into_parts();
self.path.get_mut().update(&head.uri);
let req = HttpRequest::new(
Path::new(Url::new(head.uri.clone())),
self.path,
head,
Rc::new(self.rmap),
AppConfig::new(self.config),
@ -322,9 +432,10 @@ impl TestRequest {
/// Complete request creation and generate `HttpRequest` instance
pub fn to_http_request(mut self) -> HttpRequest {
let (head, _) = self.req.finish().into_parts();
self.path.get_mut().update(&head.uri);
let mut req = HttpRequest::new(
Path::new(Url::new(head.uri.clone())),
self.path,
head,
Rc::new(self.rmap),
AppConfig::new(self.config),
@ -337,9 +448,10 @@ impl TestRequest {
/// Complete request creation and generate `HttpRequest` and `Payload` instances
pub fn to_http_parts(mut self) -> (HttpRequest, Payload) {
let (head, payload) = self.req.finish().into_parts();
self.path.get_mut().update(&head.uri);
let mut req = HttpRequest::new(
Path::new(Url::new(head.uri.clone())),
self.path,
head,
Rc::new(self.rmap),
AppConfig::new(self.config),
@ -365,55 +477,74 @@ impl TestRequest {
{
block_on(f)
}
}
/// 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":"Nikolay Kim"}"#.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"))
#[cfg(test)]
mod tests {
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
use super::*;
use crate::{http::header, web, App, HttpResponse};
#[test]
fn test_basics() {
let req = TestRequest::with_hdr(header::ContentType::json())
.version(Version::HTTP_2)
.set(header::Date(SystemTime::now().into()))
.param("test", "123")
.app_data(10u32)
.to_http_request();
assert!(req.headers().contains_key(header::CONTENT_TYPE));
assert!(req.headers().contains_key(header::DATE));
assert_eq!(&req.match_info()["test"], "123");
assert_eq!(req.version(), Version::HTTP_2);
let data = req.app_data::<u32>().unwrap();
assert_eq!(*data, 10);
assert_eq!(*data.get_ref(), 10);
}
#[test]
fn test_response() {
let mut app = init_service(
App::new().service(
web::resource("/index.html")
.route(web::post().to(|| HttpResponse::Ok().body("welcome!"))),
),
);
let req = TestRequest::post()
.uri("/index.html")
.header(header::CONTENT_TYPE, "application/json")
.to_request();
let result = read_response(&mut app, req);
assert_eq!(result, Bytes::from_static(b"welcome!"));
}
#[derive(Serialize, Deserialize)]
pub struct Person {
id: String,
name: String,
}
#[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}
///
pub fn get() -> Route {
Route::new().method(Method::GET)
method(Method::GET)
}
/// Create *route* with `POST` method guard.
@ -123,7 +123,7 @@ pub fn get() -> Route {
/// * /{project_id}
///
pub fn post() -> Route {
Route::new().method(Method::POST)
method(Method::POST)
}
/// Create *route* with `PUT` method guard.
@ -143,7 +143,7 @@ pub fn post() -> Route {
/// * /{project_id}
///
pub fn put() -> Route {
Route::new().method(Method::PUT)
method(Method::PUT)
}
/// Create *route* with `PATCH` method guard.
@ -163,7 +163,7 @@ pub fn put() -> Route {
/// * /{project_id}
///
pub fn patch() -> Route {
Route::new().method(Method::PATCH)
method(Method::PATCH)
}
/// Create *route* with `DELETE` method guard.
@ -183,7 +183,7 @@ pub fn patch() -> Route {
/// * /{project_id}
///
pub fn delete() -> Route {
Route::new().method(Method::DELETE)
method(Method::DELETE)
}
/// Create *route* with `HEAD` method guard.
@ -203,7 +203,7 @@ pub fn delete() -> Route {
/// * /{project_id}
///
pub fn head() -> Route {
Route::new().method(Method::HEAD)
method(Method::HEAD)
}
/// Create *route* and add method guard.