From 0ad02ee0e02c235b49a181bd164cc945fde18b18 Mon Sep 17 00:00:00 2001
From: Tore Pettersen <toreskog@live.com>
Date: Sun, 5 Apr 2020 21:12:44 +0200
Subject: [PATCH] Add convenience functions for testing (#1401)

* Add convenience functions for testing

* Fix remarks from PR and add tests

* Add unpin to read_json_body

* Update changelog
---
 CHANGES.md  |  1 +
 src/test.rs | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 98 insertions(+)

diff --git a/CHANGES.md b/CHANGES.md
index 0739f280c..4a7862543 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -14,6 +14,7 @@
 ### Added
 
 * Add helper function for creating routes with `TRACE` method guard `web::trace()`
+* Add convenience functions `test::read_body_json()` and `test::TestRequest::send_request()` for testing.
 
 ### Changed
 
diff --git a/src/test.rs b/src/test.rs
index 15e66a230..19ea8bbef 100644
--- a/src/test.rs
+++ b/src/test.rs
@@ -203,6 +203,54 @@ where
     bytes.freeze()
 }
 
+/// Helper function that returns a deserialized response body of a ServiceResponse.
+///
+/// ```rust
+/// use actix_web::{App, test, web, HttpResponse, http::header};
+/// use serde::{Serialize, Deserialize};
+///
+/// #[derive(Serialize, Deserialize)]
+/// pub struct Person {
+///     id: String,
+///     name: String,
+/// }
+///
+/// #[actix_rt::test]
+/// async fn test_post_person() {
+///     let mut app = test::init_service(
+///         App::new().service(
+///             web::resource("/people")
+///                 .route(web::post().to(|person: web::Json<Person>| async {
+///                     HttpResponse::Ok()
+///                         .json(person.into_inner())})
+///                     ))
+///     ).await;
+///
+///     let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
+///
+///     let resp = test::TestRequest::post()
+///         .uri("/people")
+///         .header(header::CONTENT_TYPE, "application/json")
+///         .set_payload(payload)
+///         .send_request(&mut app)
+///         .await;
+///
+///     assert!(resp.status().is_success());
+///
+///     let result: Person = test::read_body_json(resp).await;
+/// }
+/// ```
+pub async fn read_body_json<T, B>(res: ServiceResponse<B>) -> T
+where
+    B: MessageBody + Unpin,
+    T: DeserializeOwned,
+{
+    let body = read_body(res).await;
+
+    serde_json::from_slice(&body)
+        .unwrap_or_else(|_| panic!("read_response_json failed during deserialization"))
+}
+
 pub async fn load_stream<S>(mut stream: S) -> Result<Bytes, Error>
 where
     S: Stream<Item = Result<Bytes, Error>> + Unpin,
@@ -527,6 +575,16 @@ impl TestRequest {
 
         (req, payload)
     }
+
+    /// Complete request creation, calls service and waits for response future completion.
+    pub async fn send_request<S, B, E>(self, app: &mut S) -> S::Response
+    where
+        S: Service<Request = Request, Response = ServiceResponse<B>, Error = E>,
+        E: std::fmt::Debug
+    {
+        let req = self.to_request();
+        call_service(app, req).await
+    }
 }
 
 /// Start test server with default configuration
@@ -1041,6 +1099,23 @@ mod tests {
         assert_eq!(result, Bytes::from_static(b"welcome!"));
     }
 
+    #[actix_rt::test]
+    async fn test_send_request() {
+        let mut app =
+            init_service(App::new().service(web::resource("/index.html").route(
+                web::get().to(|| async { HttpResponse::Ok().body("welcome!") }),
+            )))
+            .await;
+
+        let resp = TestRequest::get()
+            .uri("/index.html")
+            .send_request(&mut app)
+            .await;
+
+        let result = read_body(resp).await;
+        assert_eq!(result, Bytes::from_static(b"welcome!"));
+    }
+
     #[derive(Serialize, Deserialize)]
     pub struct Person {
         id: String,
@@ -1068,6 +1143,28 @@ mod tests {
         assert_eq!(&result.id, "12345");
     }
 
+    #[actix_rt::test]
+    async fn test_body_json() {
+        let mut app = init_service(App::new().service(web::resource("/people").route(
+            web::post().to(|person: web::Json<Person>| {
+                async { HttpResponse::Ok().json(person.into_inner()) }
+            }),
+        )))
+        .await;
+
+        let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
+
+        let resp = TestRequest::post()
+            .uri("/people")
+            .header(header::CONTENT_TYPE, "application/json")
+            .set_payload(payload)
+            .send_request(&mut app)
+            .await;
+
+        let result: Person = read_body_json(resp).await;
+        assert_eq!(&result.name, "User name");
+    }
+
     #[actix_rt::test]
     async fn test_request_response_form() {
         let mut app = init_service(App::new().service(web::resource("/people").route(