diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 8ea60266e..6fc3c93d7 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -7,6 +7,7 @@ - Add `Logger::custom_response_replace()`. [#2631] - Add rudimentary redirection service at `web::redirect()` / `web::Redirect`. [#1961] - Add `guard::Acceptable` for matching against `Accept` header mime types. [#2265] +- Add fallible versions of test helpers: `try_call_service`, `try_call_and_read_body_json`, `try_read_body`, and `try_read_body_json`. [#2961] ### Fixed - Add `Allow` header to `Resource`'s default responses when no routes are matched. [#2949] @@ -17,7 +18,7 @@ [#2784]: https://github.com/actix/actix-web/pull/2784 [#2867]: https://github.com/actix/actix-web/pull/2867 [#2949]: https://github.com/actix/actix-web/pull/2949 - +[#2961]: https://github.com/actix/actix-web/pull/2961 ## 4.2.1 - 2022-09-12 ### Fixed diff --git a/actix-web/src/test/mod.rs b/actix-web/src/test/mod.rs index 9c6121151..5d9367b82 100644 --- a/actix-web/src/test/mod.rs +++ b/actix-web/src/test/mod.rs @@ -10,12 +10,16 @@ //! # Calling Test Service //! - [`TestRequest`] //! - [`call_service`] +//! - [`try_call_service`] //! - [`call_and_read_body`] //! - [`call_and_read_body_json`] +//! - [`try_call_and_read_body_json`] //! //! # Reading Response Payloads //! - [`read_body`] +//! - [`try_read_body`] //! - [`read_body_json`] +//! - [`try_read_body_json`] // TODO: more docs on generally how testing works with these parts @@ -31,7 +35,8 @@ pub use self::test_services::{default_service, ok_service, simple_service, statu #[allow(deprecated)] pub use self::test_utils::{ call_and_read_body, call_and_read_body_json, call_service, init_service, read_body, - read_body_json, read_response, read_response_json, + read_body_json, read_response, read_response_json, try_call_and_read_body_json, + try_call_service, try_read_body, try_read_body_json, }; #[cfg(test)] diff --git a/actix-web/src/test/test_utils.rs b/actix-web/src/test/test_utils.rs index 6f0926f35..398b29605 100644 --- a/actix-web/src/test/test_utils.rs +++ b/actix-web/src/test/test_utils.rs @@ -100,6 +100,15 @@ where .expect("test service call returned error") } +/// Fallible version of [`call_service`] that allows testing response completion errors. +pub async fn try_call_service(app: &S, req: R) -> Result +where + S: Service, Error = E>, + E: std::fmt::Debug, +{ + app.call(req).await +} + /// Helper function that returns a response body of a TestRequest /// /// # Examples @@ -185,13 +194,23 @@ pub async fn read_body(res: ServiceResponse) -> Bytes where B: MessageBody, { - let body = res.into_body(); - body::to_bytes(body) + try_read_body(res) .await .map_err(Into::>::into) .expect("error reading test response body") } +/// Fallible version of [`read_body`] that allows testing MessageBody reading errors. +pub async fn try_read_body( + res: ServiceResponse, +) -> Result::Error> +where + B: MessageBody, +{ + let body = res.into_body(); + body::to_bytes(body).await +} + /// Helper function that returns a deserialized response body of a ServiceResponse. /// /// # Examples @@ -240,18 +259,27 @@ where B: MessageBody, T: DeserializeOwned, { - let body = read_body(res).await; - - serde_json::from_slice(&body).unwrap_or_else(|err| { + try_read_body_json(res).await.unwrap_or_else(|err| { panic!( - "could not deserialize body into a {}\nerr: {}\nbody: {:?}", + "could not deserialize body into a {}\nerr: {}", std::any::type_name::(), err, - body, ) }) } +/// Fallible version of [`read_body_json`] that allows testing response deserialzation errors. +pub async fn try_read_body_json(res: ServiceResponse) -> Result> +where + B: MessageBody, + T: DeserializeOwned, +{ + let body = try_read_body(res) + .await + .map_err(Into::>::into)?; + serde_json::from_slice(&body).map_err(Into::>::into) +} + /// Helper function that returns a deserialized response body of a TestRequest /// /// # Examples @@ -299,8 +327,23 @@ where B: MessageBody, T: DeserializeOwned, { - let res = call_service(app, req).await; - read_body_json(res).await + try_call_and_read_body_json(app, req).await.unwrap() +} + +/// Fallible version of [`call_and_read_body_json`] that allows testing service call errors. +pub async fn try_call_and_read_body_json( + app: &S, + req: Request, +) -> Result> +where + S: Service, Error = Error>, + B: MessageBody, + T: DeserializeOwned, +{ + let res = try_call_service(app, req) + .await + .map_err(Into::>::into)?; + try_read_body_json(res).await } #[doc(hidden)] @@ -358,7 +401,7 @@ mod tests { assert_eq!(result, Bytes::from_static(b"delete!")); } - #[derive(Serialize, Deserialize)] + #[derive(Serialize, Deserialize, Debug)] pub struct Person { id: String, name: String, @@ -383,6 +426,26 @@ mod tests { assert_eq!(&result.id, "12345"); } + #[actix_rt::test] + async fn test_try_response_json_error() { + let app = init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), + ))) + .await; + + let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes(); + + let req = TestRequest::post() + .uri("/animals") // Not registered to ensure an error occurs. + .insert_header((header::CONTENT_TYPE, "application/json")) + .set_payload(payload) + .to_request(); + + let result: Result> = + try_call_and_read_body_json(&app, req).await; + assert!(result.is_err()); + } + #[actix_rt::test] async fn test_body_json() { let app = init_service(App::new().service(web::resource("/people").route( @@ -403,6 +466,27 @@ mod tests { assert_eq!(&result.name, "User name"); } + #[actix_rt::test] + async fn test_try_body_json_error() { + let app = init_service(App::new().service(web::resource("/people").route( + web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), + ))) + .await; + + // Use a number for id to cause a deserialization error. + let payload = r#"{"id":12345,"name":"User name"}"#.as_bytes(); + + let res = TestRequest::post() + .uri("/people") + .insert_header((header::CONTENT_TYPE, "application/json")) + .set_payload(payload) + .send_request(&app) + .await; + + let result: Result> = try_read_body_json(res).await; + assert!(result.is_err()); + } + #[actix_rt::test] async fn test_request_response_form() { let app = init_service(App::new().service(web::resource("/people").route(