mirror of
https://github.com/actix/actix-web.git
synced 2024-12-29 19:40:34 +00:00
add test::read_response; fix TestRequest::app_data()
This commit is contained in:
parent
4cc2b38059
commit
f9078d41cd
5 changed files with 213 additions and 71 deletions
12
CHANGES.md
12
CHANGES.md
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -61,6 +61,7 @@ pub(crate) trait DataFactoryResult {
|
|||
/// web::get().to(index)));
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Data<T>(Arc<T>);
|
||||
|
||||
impl<T> Data<T> {
|
||||
|
|
255
src/test.rs
255
src/test.rs
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
12
src/web.rs
12
src/web.rs
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue