//! Various helpers for Actix applications to use during testing. use std::cell::RefCell; 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::test::TestRequest as HttpTestRequest; use actix_http::{Extensions, Request}; use actix_router::{Path, ResourceDef, Url}; 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 serde::de::DeserializeOwned; use serde_json; pub use actix_http::test::TestBuffer; use crate::config::{AppConfig, AppConfigInner}; use crate::data::RouteData; use crate::dev::{Body, MessageBody, Payload}; use crate::request::HttpRequestPool; use crate::rmap::ResourceMap; use crate::service::{ServiceRequest, ServiceResponse}; use crate::{Error, HttpRequest, HttpResponse}; thread_local! { static RT: RefCell = { RefCell::new(Runtime::new().unwrap()) }; } /// Runs the provided future, blocking the current thread until the future /// completes. /// /// This function can be used to synchronously block the current thread /// until the provided `future` has resolved either successfully or with an /// error. The result of the future is then returned from this function /// call. /// /// Note that this function is intended to be used only for testing purpose. /// This function panics on nested call. pub fn block_on(f: F) -> Result where F: Future, { RT.with(move |rt| rt.borrow_mut().block_on(f)) } /// Runs the provided function, with runtime enabled. /// /// Note that this function is intended to be used only for testing purpose. /// This function panics on nested call. pub fn run_on(f: F) -> R where F: Fn() -> R, { RT.with(move |rt| rt.borrow_mut().block_on(lazy(|| Ok::<_, ()>(f())))) .unwrap() } /// Create service that always responds with `HttpResponse::Ok()` pub fn ok_service( ) -> impl Service, Error = Error> { default_service(StatusCode::OK) } /// Create service that responds with response with specified status code pub fn default_service( status_code: StatusCode, ) -> impl Service, Error = Error> { FnService::new(move |req: ServiceRequest| { req.into_response(HttpResponse::build(status_code).finish()) }) } /// This method accepts application builder instance, and constructs /// service. /// /// ```rust,ignore /// use actix_service::Service; /// use actix_web::{test, web, App, HttpResponse, http::StatusCode}; /// /// fn main() { /// let mut app = test::init_service( /// App::new() /// .service(web::resource("/test").to(|| HttpResponse::Ok())) /// ); /// /// // Create request object /// let req = test::TestRequest::with_uri("/test").to_request(); /// /// // Execute application /// let resp = test::block_on(app.call(req)).unwrap(); /// assert_eq!(resp.status(), StatusCode::OK); /// } /// ``` pub fn init_service( app: R, ) -> impl Service, Error = E> where R: IntoNewService, S: NewService< ServerConfig, Request = Request, Response = ServiceResponse, Error = E, >, S::InitError: std::fmt::Debug, { let cfg = ServerConfig::new("127.0.0.1:8080".parse().unwrap()); block_on(app.into_new_service().new_service(&cfg)).unwrap() } /// Calls service and waits for response future completion. /// /// ```rust,ignore /// use actix_web::{test, App, HttpResponse, http::StatusCode}; /// use actix_service::Service; /// /// fn main() { /// let mut app = test::init_service( /// App::new() /// .service(web::resource("/test").to(|| HttpResponse::Ok())) /// ); /// /// // Create request object /// let req = test::TestRequest::with_uri("/test").to_request(); /// /// // Call application /// let resp = test::call_success(&mut app, req); /// assert_eq!(resp.status(), StatusCode::OK); /// } /// ``` pub fn call_success(app: &mut S, req: R) -> S::Response where S: Service, Error = E>, E: std::fmt::Debug, { block_on(app.call(req)).unwrap() } /// Test `Request` builder. /// /// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern. /// You can generate various types of request via TestRequest's methods: /// * `TestRequest::to_request` creates `actix_http::Request` instance. /// * `TestRequest::to_service` creates `ServiceRequest` instance, which is used for testing middlewares and chain adapters. /// * `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 /// # use futures::IntoFuture; /// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; /// use actix_web::http::{header, StatusCode}; /// /// fn index(req: HttpRequest) -> HttpResponse { /// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { /// HttpResponse::Ok().into() /// } else { /// 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).into_future()).unwrap(); /// assert_eq!(resp.status(), StatusCode::OK); /// /// let req = test::TestRequest::default().to_http_request(); /// let resp = test::block_on(index(req).into_future()).unwrap(); /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` pub struct TestRequest { req: HttpTestRequest, rmap: ResourceMap, config: AppConfigInner, route_data: Extensions, } impl Default for TestRequest { fn default() -> TestRequest { TestRequest { req: HttpTestRequest::default(), rmap: ResourceMap::new(ResourceDef::new("")), config: AppConfigInner::default(), route_data: Extensions::new(), } } } #[allow(clippy::wrong_self_convention)] impl TestRequest { /// Create TestRequest and set request uri pub fn with_uri(path: &str) -> TestRequest { TestRequest::default().uri(path) } /// Create TestRequest and set header pub fn with_hdr(hdr: H) -> TestRequest { TestRequest::default().set(hdr) } /// Create TestRequest and set header pub fn with_header(key: K, value: V) -> TestRequest where HeaderName: HttpTryFrom, V: IntoHeaderValue, { TestRequest::default().header(key, value) } /// Create TestRequest and set method to `Method::GET` pub fn get() -> TestRequest { TestRequest::default().method(Method::GET) } /// Create TestRequest and set method to `Method::POST` pub fn post() -> TestRequest { TestRequest::default().method(Method::POST) } /// Set HTTP version of this request pub fn version(mut self, ver: Version) -> Self { self.req.version(ver); self } /// Set HTTP method of this request pub fn method(mut self, meth: Method) -> Self { self.req.method(meth); self } /// Set HTTP Uri of this request pub fn uri(mut self, path: &str) -> Self { self.req.uri(path); self } /// Set a header pub fn set(mut self, hdr: H) -> Self { self.req.set(hdr); self } /// Set a header pub fn header(mut self, key: K, value: V) -> Self where HeaderName: HttpTryFrom, V: IntoHeaderValue, { self.req.header(key, value); self } /// Set cookie for this request pub fn cookie(mut self, cookie: Cookie) -> Self { self.req.cookie(cookie); self } /// Set request payload pub fn set_payload>(mut self, data: B) -> Self { self.req.set_payload(data); self } /// Set application data. This is equivalent of `App::data()` method /// for testing purpose. pub fn app_data(self, data: T) -> Self { self.config.extensions.borrow_mut().insert(data); self } /// Set route data. This is equivalent of `Route::data()` method /// for testing purpose. pub fn route_data(mut self, data: T) -> Self { self.route_data.insert(RouteData::new(data)); self } #[cfg(test)] /// Set request config pub(crate) fn rmap(mut self, rmap: ResourceMap) -> Self { self.rmap = rmap; self } /// Complete request creation and generate `Request` instance pub fn to_request(mut self) -> Request { self.req.finish() } /// Complete request creation and generate `ServiceRequest` instance pub fn to_srv_request(mut self) -> ServiceRequest { let (head, payload) = self.req.finish().into_parts(); let req = HttpRequest::new( Path::new(Url::new(head.uri.clone())), head, Rc::new(self.rmap), AppConfig::new(self.config), HttpRequestPool::create(), ); ServiceRequest::from_parts(req, payload) } /// Complete request creation and generate `ServiceResponse` instance pub fn to_srv_response(self, res: HttpResponse) -> ServiceResponse { self.to_srv_request().into_response(res) } /// Complete request creation and generate `HttpRequest` instance pub fn to_http_request(mut self) -> HttpRequest { let (head, _) = self.req.finish().into_parts(); let mut req = HttpRequest::new( Path::new(Url::new(head.uri.clone())), head, Rc::new(self.rmap), AppConfig::new(self.config), HttpRequestPool::create(), ); req.set_route_data(Some(Rc::new(self.route_data))); req } /// 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(); let mut req = HttpRequest::new( Path::new(Url::new(head.uri.clone())), head, Rc::new(self.rmap), AppConfig::new(self.config), HttpRequestPool::create(), ); req.set_route_data(Some(Rc::new(self.route_data))); (req, payload) } /// Runs the provided future, blocking the current thread until the future /// completes. /// /// This function can be used to synchronously block the current thread /// until the provided `future` has resolved either successfully or with an /// error. The result of the future is then returned from this function /// call. /// /// Note that this function is intended to be used only for testing purpose. /// This function panics on nested call. pub fn block_on(f: F) -> Result where F: Future, { 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| { /// 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(app: &mut S, req: Request) -> T where S: Service, Error = Error>, B: MessageBody, T: DeserializeOwned, { block_on(app.call(req).and_then(|mut resp: ServiceResponse| { 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")) } }