1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-11-29 21:11:17 +00:00

Add fallible versions of test_utils helpers to actix-test (#2961)

Co-authored-by: Rob Ede <robjtede@icloud.com>
This commit is contained in:
Zach 2023-01-11 03:43:51 -08:00 committed by GitHub
parent b9f54c8796
commit 6627109984
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 102 additions and 12 deletions

View file

@ -7,6 +7,7 @@
- Add `Logger::custom_response_replace()`. [#2631] - Add `Logger::custom_response_replace()`. [#2631]
- Add rudimentary redirection service at `web::redirect()` / `web::Redirect`. [#1961] - Add rudimentary redirection service at `web::redirect()` / `web::Redirect`. [#1961]
- Add `guard::Acceptable` for matching against `Accept` header mime types. [#2265] - 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 ### Fixed
- Add `Allow` header to `Resource`'s default responses when no routes are matched. [#2949] - 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 [#2784]: https://github.com/actix/actix-web/pull/2784
[#2867]: https://github.com/actix/actix-web/pull/2867 [#2867]: https://github.com/actix/actix-web/pull/2867
[#2949]: https://github.com/actix/actix-web/pull/2949 [#2949]: https://github.com/actix/actix-web/pull/2949
[#2961]: https://github.com/actix/actix-web/pull/2961
## 4.2.1 - 2022-09-12 ## 4.2.1 - 2022-09-12
### Fixed ### Fixed

View file

@ -10,12 +10,16 @@
//! # Calling Test Service //! # Calling Test Service
//! - [`TestRequest`] //! - [`TestRequest`]
//! - [`call_service`] //! - [`call_service`]
//! - [`try_call_service`]
//! - [`call_and_read_body`] //! - [`call_and_read_body`]
//! - [`call_and_read_body_json`] //! - [`call_and_read_body_json`]
//! - [`try_call_and_read_body_json`]
//! //!
//! # Reading Response Payloads //! # Reading Response Payloads
//! - [`read_body`] //! - [`read_body`]
//! - [`try_read_body`]
//! - [`read_body_json`] //! - [`read_body_json`]
//! - [`try_read_body_json`]
// TODO: more docs on generally how testing works with these parts // 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)] #[allow(deprecated)]
pub use self::test_utils::{ pub use self::test_utils::{
call_and_read_body, call_and_read_body_json, call_service, init_service, read_body, 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)] #[cfg(test)]

View file

@ -100,6 +100,15 @@ where
.expect("test service call returned error") .expect("test service call returned error")
} }
/// Fallible version of [`call_service`] that allows testing response completion errors.
pub async fn try_call_service<S, R, B, E>(app: &S, req: R) -> Result<S::Response, E>
where
S: Service<R, Response = ServiceResponse<B>, Error = E>,
E: std::fmt::Debug,
{
app.call(req).await
}
/// Helper function that returns a response body of a TestRequest /// Helper function that returns a response body of a TestRequest
/// ///
/// # Examples /// # Examples
@ -185,13 +194,23 @@ pub async fn read_body<B>(res: ServiceResponse<B>) -> Bytes
where where
B: MessageBody, B: MessageBody,
{ {
let body = res.into_body(); try_read_body(res)
body::to_bytes(body)
.await .await
.map_err(Into::<Box<dyn StdError>>::into) .map_err(Into::<Box<dyn StdError>>::into)
.expect("error reading test response body") .expect("error reading test response body")
} }
/// Fallible version of [`read_body`] that allows testing MessageBody reading errors.
pub async fn try_read_body<B>(
res: ServiceResponse<B>,
) -> Result<Bytes, <B as MessageBody>::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. /// Helper function that returns a deserialized response body of a ServiceResponse.
/// ///
/// # Examples /// # Examples
@ -240,18 +259,27 @@ where
B: MessageBody, B: MessageBody,
T: DeserializeOwned, T: DeserializeOwned,
{ {
let body = read_body(res).await; try_read_body_json(res).await.unwrap_or_else(|err| {
serde_json::from_slice(&body).unwrap_or_else(|err| {
panic!( panic!(
"could not deserialize body into a {}\nerr: {}\nbody: {:?}", "could not deserialize body into a {}\nerr: {}",
std::any::type_name::<T>(), std::any::type_name::<T>(),
err, err,
body,
) )
}) })
} }
/// Fallible version of [`read_body_json`] that allows testing response deserialzation errors.
pub async fn try_read_body_json<T, B>(res: ServiceResponse<B>) -> Result<T, Box<dyn StdError>>
where
B: MessageBody,
T: DeserializeOwned,
{
let body = try_read_body(res)
.await
.map_err(Into::<Box<dyn StdError>>::into)?;
serde_json::from_slice(&body).map_err(Into::<Box<dyn StdError>>::into)
}
/// Helper function that returns a deserialized response body of a TestRequest /// Helper function that returns a deserialized response body of a TestRequest
/// ///
/// # Examples /// # Examples
@ -299,8 +327,23 @@ where
B: MessageBody, B: MessageBody,
T: DeserializeOwned, T: DeserializeOwned,
{ {
let res = call_service(app, req).await; try_call_and_read_body_json(app, req).await.unwrap()
read_body_json(res).await }
/// Fallible version of [`call_and_read_body_json`] that allows testing service call errors.
pub async fn try_call_and_read_body_json<S, B, T>(
app: &S,
req: Request,
) -> Result<T, Box<dyn StdError>>
where
S: Service<Request, Response = ServiceResponse<B>, Error = Error>,
B: MessageBody,
T: DeserializeOwned,
{
let res = try_call_service(app, req)
.await
.map_err(Into::<Box<dyn StdError>>::into)?;
try_read_body_json(res).await
} }
#[doc(hidden)] #[doc(hidden)]
@ -358,7 +401,7 @@ mod tests {
assert_eq!(result, Bytes::from_static(b"delete!")); assert_eq!(result, Bytes::from_static(b"delete!"));
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Debug)]
pub struct Person { pub struct Person {
id: String, id: String,
name: String, name: String,
@ -383,6 +426,26 @@ mod tests {
assert_eq!(&result.id, "12345"); 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<Person>| 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<Person, Box<dyn StdError>> =
try_call_and_read_body_json(&app, req).await;
assert!(result.is_err());
}
#[actix_rt::test] #[actix_rt::test]
async fn test_body_json() { async fn test_body_json() {
let app = init_service(App::new().service(web::resource("/people").route( let app = init_service(App::new().service(web::resource("/people").route(
@ -403,6 +466,27 @@ mod tests {
assert_eq!(&result.name, "User name"); 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<Person>| 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<Person, Box<dyn StdError>> = try_read_body_json(res).await;
assert!(result.is_err());
}
#[actix_rt::test] #[actix_rt::test]
async fn test_request_response_form() { async fn test_request_response_form() {
let app = init_service(App::new().service(web::resource("/people").route( let app = init_service(App::new().service(web::resource("/people").route(