actix_web/test/
test_utils.rs

1use std::error::Error as StdError;
2
3use actix_http::Request;
4use actix_service::IntoServiceFactory;
5use serde::de::DeserializeOwned;
6
7use crate::{
8    body::{self, MessageBody},
9    config::AppConfig,
10    dev::{Service, ServiceFactory},
11    service::ServiceResponse,
12    web::Bytes,
13    Error,
14};
15
16/// Initialize service from application builder instance.
17///
18/// # Examples
19/// ```
20/// use actix_service::Service;
21/// use actix_web::{test, web, App, HttpResponse, http::StatusCode};
22///
23/// #[actix_web::test]
24/// async fn test_init_service() {
25///     let app = test::init_service(
26///         App::new()
27///             .service(web::resource("/test").to(|| async { "OK" }))
28///     ).await;
29///
30///     // Create request object
31///     let req = test::TestRequest::with_uri("/test").to_request();
32///
33///     // Execute application
34///     let res = app.call(req).await.unwrap();
35///     assert_eq!(res.status(), StatusCode::OK);
36/// }
37/// ```
38///
39/// # Panics
40/// Panics if service initialization returns an error.
41pub async fn init_service<R, S, B, E>(
42    app: R,
43) -> impl Service<Request, Response = ServiceResponse<B>, Error = E>
44where
45    R: IntoServiceFactory<S, Request>,
46    S: ServiceFactory<Request, Config = AppConfig, Response = ServiceResponse<B>, Error = E>,
47    S::InitError: std::fmt::Debug,
48{
49    try_init_service(app)
50        .await
51        .expect("service initialization failed")
52}
53
54/// Fallible version of [`init_service`] that allows testing initialization errors.
55pub(crate) async fn try_init_service<R, S, B, E>(
56    app: R,
57) -> Result<impl Service<Request, Response = ServiceResponse<B>, Error = E>, S::InitError>
58where
59    R: IntoServiceFactory<S, Request>,
60    S: ServiceFactory<Request, Config = AppConfig, Response = ServiceResponse<B>, Error = E>,
61    S::InitError: std::fmt::Debug,
62{
63    let srv = app.into_factory();
64    srv.new_service(AppConfig::default()).await
65}
66
67/// Calls service and waits for response future completion.
68///
69/// # Examples
70/// ```
71/// use actix_web::{test, web, App, HttpResponse, http::StatusCode};
72///
73/// #[actix_web::test]
74/// async fn test_response() {
75///     let app = test::init_service(
76///         App::new()
77///             .service(web::resource("/test").to(|| async {
78///                 HttpResponse::Ok()
79///             }))
80///     ).await;
81///
82///     // Create request object
83///     let req = test::TestRequest::with_uri("/test").to_request();
84///
85///     // Call application
86///     let res = test::call_service(&app, req).await;
87///     assert_eq!(res.status(), StatusCode::OK);
88/// }
89/// ```
90///
91/// # Panics
92/// Panics if service call returns error. To handle errors use `app.call(req)`.
93pub async fn call_service<S, R, B, E>(app: &S, req: R) -> S::Response
94where
95    S: Service<R, Response = ServiceResponse<B>, Error = E>,
96    E: std::fmt::Debug,
97{
98    app.call(req)
99        .await
100        .expect("test service call returned error")
101}
102
103/// Fallible version of [`call_service`] that allows testing response completion errors.
104pub async fn try_call_service<S, R, B, E>(app: &S, req: R) -> Result<S::Response, E>
105where
106    S: Service<R, Response = ServiceResponse<B>, Error = E>,
107    E: std::fmt::Debug,
108{
109    app.call(req).await
110}
111
112/// Helper function that returns a response body of a TestRequest
113///
114/// # Examples
115/// ```
116/// use actix_web::{test, web, App, HttpResponse, http::header};
117/// use bytes::Bytes;
118///
119/// #[actix_web::test]
120/// async fn test_index() {
121///     let app = test::init_service(
122///         App::new().service(
123///             web::resource("/index.html")
124///                 .route(web::post().to(|| async {
125///                     HttpResponse::Ok().body("welcome!")
126///                 })))
127///     ).await;
128///
129///     let req = test::TestRequest::post()
130///         .uri("/index.html")
131///         .header(header::CONTENT_TYPE, "application/json")
132///         .to_request();
133///
134///     let result = test::call_and_read_body(&app, req).await;
135///     assert_eq!(result, Bytes::from_static(b"welcome!"));
136/// }
137/// ```
138///
139/// # Panics
140/// Panics if:
141/// - service call returns error;
142/// - body yields an error while it is being read.
143pub async fn call_and_read_body<S, B>(app: &S, req: Request) -> Bytes
144where
145    S: Service<Request, Response = ServiceResponse<B>, Error = Error>,
146    B: MessageBody,
147{
148    let res = call_service(app, req).await;
149    read_body(res).await
150}
151
152#[doc(hidden)]
153#[deprecated(since = "4.0.0", note = "Renamed to `call_and_read_body`.")]
154pub async fn read_response<S, B>(app: &S, req: Request) -> Bytes
155where
156    S: Service<Request, Response = ServiceResponse<B>, Error = Error>,
157    B: MessageBody,
158{
159    let res = call_service(app, req).await;
160    read_body(res).await
161}
162
163/// Helper function that returns a response body of a ServiceResponse.
164///
165/// # Examples
166/// ```
167/// use actix_web::{test, web, App, HttpResponse, http::header};
168/// use bytes::Bytes;
169///
170/// #[actix_web::test]
171/// async fn test_index() {
172///     let app = test::init_service(
173///         App::new().service(
174///             web::resource("/index.html")
175///                 .route(web::post().to(|| async {
176///                     HttpResponse::Ok().body("welcome!")
177///                 })))
178///     ).await;
179///
180///     let req = test::TestRequest::post()
181///         .uri("/index.html")
182///         .header(header::CONTENT_TYPE, "application/json")
183///         .to_request();
184///
185///     let res = test::call_service(&app, req).await;
186///     let result = test::read_body(res).await;
187///     assert_eq!(result, Bytes::from_static(b"welcome!"));
188/// }
189/// ```
190///
191/// # Panics
192/// Panics if body yields an error while it is being read.
193pub async fn read_body<B>(res: ServiceResponse<B>) -> Bytes
194where
195    B: MessageBody,
196{
197    try_read_body(res)
198        .await
199        .map_err(Into::<Box<dyn StdError>>::into)
200        .expect("error reading test response body")
201}
202
203/// Fallible version of [`read_body`] that allows testing MessageBody reading errors.
204pub async fn try_read_body<B>(res: ServiceResponse<B>) -> Result<Bytes, <B as MessageBody>::Error>
205where
206    B: MessageBody,
207{
208    let body = res.into_body();
209    body::to_bytes(body).await
210}
211
212/// Helper function that returns a deserialized response body of a ServiceResponse.
213///
214/// # Examples
215/// ```
216/// use actix_web::{App, test, web, HttpResponse, http::header};
217/// use serde::{Serialize, Deserialize};
218///
219/// #[derive(Serialize, Deserialize)]
220/// pub struct Person {
221///     id: String,
222///     name: String,
223/// }
224///
225/// #[actix_web::test]
226/// async fn test_post_person() {
227///     let app = test::init_service(
228///         App::new().service(
229///             web::resource("/people")
230///                 .route(web::post().to(|person: web::Json<Person>| async {
231///                     HttpResponse::Ok()
232///                         .json(person)})
233///                     ))
234///     ).await;
235///
236///     let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
237///
238///     let res = test::TestRequest::post()
239///         .uri("/people")
240///         .header(header::CONTENT_TYPE, "application/json")
241///         .set_payload(payload)
242///         .send_request(&mut app)
243///         .await;
244///
245///     assert!(res.status().is_success());
246///
247///     let result: Person = test::read_body_json(res).await;
248/// }
249/// ```
250///
251/// # Panics
252/// Panics if:
253/// - body yields an error while it is being read;
254/// - received body is not a valid JSON representation of `T`.
255pub async fn read_body_json<T, B>(res: ServiceResponse<B>) -> T
256where
257    B: MessageBody,
258    T: DeserializeOwned,
259{
260    try_read_body_json(res).await.unwrap_or_else(|err| {
261        panic!(
262            "could not deserialize body into a {}\nerr: {}",
263            std::any::type_name::<T>(),
264            err,
265        )
266    })
267}
268
269/// Fallible version of [`read_body_json`] that allows testing response deserialization errors.
270pub async fn try_read_body_json<T, B>(res: ServiceResponse<B>) -> Result<T, Box<dyn StdError>>
271where
272    B: MessageBody,
273    T: DeserializeOwned,
274{
275    let body = try_read_body(res)
276        .await
277        .map_err(Into::<Box<dyn StdError>>::into)?;
278    serde_json::from_slice(&body).map_err(Into::<Box<dyn StdError>>::into)
279}
280
281/// Helper function that returns a deserialized response body of a TestRequest
282///
283/// # Examples
284/// ```
285/// use actix_web::{App, test, web, HttpResponse, http::header};
286/// use serde::{Serialize, Deserialize};
287///
288/// #[derive(Serialize, Deserialize)]
289/// pub struct Person {
290///     id: String,
291///     name: String
292/// }
293///
294/// #[actix_web::test]
295/// async fn test_add_person() {
296///     let app = test::init_service(
297///         App::new().service(
298///             web::resource("/people")
299///                 .route(web::post().to(|person: web::Json<Person>| async {
300///                     HttpResponse::Ok()
301///                         .json(person)})
302///                     ))
303///     ).await;
304///
305///     let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
306///
307///     let req = test::TestRequest::post()
308///         .uri("/people")
309///         .header(header::CONTENT_TYPE, "application/json")
310///         .set_payload(payload)
311///         .to_request();
312///
313///     let result: Person = test::call_and_read_body_json(&mut app, req).await;
314/// }
315/// ```
316///
317/// # Panics
318/// Panics if:
319/// - service call returns an error body yields an error while it is being read;
320/// - body yields an error while it is being read;
321/// - received body is not a valid JSON representation of `T`.
322pub async fn call_and_read_body_json<S, B, T>(app: &S, req: Request) -> T
323where
324    S: Service<Request, Response = ServiceResponse<B>, Error = Error>,
325    B: MessageBody,
326    T: DeserializeOwned,
327{
328    try_call_and_read_body_json(app, req).await.unwrap()
329}
330
331/// Fallible version of [`call_and_read_body_json`] that allows testing service call errors.
332pub async fn try_call_and_read_body_json<S, B, T>(
333    app: &S,
334    req: Request,
335) -> Result<T, Box<dyn StdError>>
336where
337    S: Service<Request, Response = ServiceResponse<B>, Error = Error>,
338    B: MessageBody,
339    T: DeserializeOwned,
340{
341    let res = try_call_service(app, req)
342        .await
343        .map_err(Into::<Box<dyn StdError>>::into)?;
344    try_read_body_json(res).await
345}
346
347#[doc(hidden)]
348#[deprecated(since = "4.0.0", note = "Renamed to `call_and_read_body_json`.")]
349pub async fn read_response_json<S, B, T>(app: &S, req: Request) -> T
350where
351    S: Service<Request, Response = ServiceResponse<B>, Error = Error>,
352    B: MessageBody,
353    T: DeserializeOwned,
354{
355    call_and_read_body_json(app, req).await
356}
357
358#[cfg(test)]
359mod tests {
360    use serde::{Deserialize, Serialize};
361
362    use super::*;
363    use crate::{
364        dev::ServiceRequest, http::header, test::TestRequest, web, App, HttpMessage, HttpResponse,
365    };
366
367    #[actix_rt::test]
368    async fn test_request_methods() {
369        let app = init_service(
370            App::new().service(
371                web::resource("/index.html")
372                    .route(web::put().to(|| HttpResponse::Ok().body("put!")))
373                    .route(web::patch().to(|| HttpResponse::Ok().body("patch!")))
374                    .route(web::delete().to(|| HttpResponse::Ok().body("delete!"))),
375            ),
376        )
377        .await;
378
379        let put_req = TestRequest::put()
380            .uri("/index.html")
381            .insert_header((header::CONTENT_TYPE, "application/json"))
382            .to_request();
383
384        let result = call_and_read_body(&app, put_req).await;
385        assert_eq!(result, Bytes::from_static(b"put!"));
386
387        let patch_req = TestRequest::patch()
388            .uri("/index.html")
389            .insert_header((header::CONTENT_TYPE, "application/json"))
390            .to_request();
391
392        let result = call_and_read_body(&app, patch_req).await;
393        assert_eq!(result, Bytes::from_static(b"patch!"));
394
395        let delete_req = TestRequest::delete().uri("/index.html").to_request();
396        let result = call_and_read_body(&app, delete_req).await;
397        assert_eq!(result, Bytes::from_static(b"delete!"));
398    }
399
400    #[derive(Serialize, Deserialize, Debug)]
401    pub struct Person {
402        id: String,
403        name: String,
404    }
405
406    #[actix_rt::test]
407    async fn test_response_json() {
408        let app =
409            init_service(App::new().service(web::resource("/people").route(
410                web::post().to(|person: web::Json<Person>| HttpResponse::Ok().json(person)),
411            )))
412            .await;
413
414        let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
415
416        let req = TestRequest::post()
417            .uri("/people")
418            .insert_header((header::CONTENT_TYPE, "application/json"))
419            .set_payload(payload)
420            .to_request();
421
422        let result: Person = call_and_read_body_json(&app, req).await;
423        assert_eq!(&result.id, "12345");
424    }
425
426    #[actix_rt::test]
427    async fn test_try_response_json_error() {
428        let app =
429            init_service(App::new().service(web::resource("/people").route(
430                web::post().to(|person: web::Json<Person>| HttpResponse::Ok().json(person)),
431            )))
432            .await;
433
434        let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
435
436        let req = TestRequest::post()
437            .uri("/animals") // Not registered to ensure an error occurs.
438            .insert_header((header::CONTENT_TYPE, "application/json"))
439            .set_payload(payload)
440            .to_request();
441
442        let result: Result<Person, Box<dyn StdError>> =
443            try_call_and_read_body_json(&app, req).await;
444        assert!(result.is_err());
445    }
446
447    #[actix_rt::test]
448    async fn test_body_json() {
449        let app =
450            init_service(App::new().service(web::resource("/people").route(
451                web::post().to(|person: web::Json<Person>| HttpResponse::Ok().json(person)),
452            )))
453            .await;
454
455        let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
456
457        let res = TestRequest::post()
458            .uri("/people")
459            .insert_header((header::CONTENT_TYPE, "application/json"))
460            .set_payload(payload)
461            .send_request(&app)
462            .await;
463
464        let result: Person = read_body_json(res).await;
465        assert_eq!(&result.name, "User name");
466    }
467
468    #[actix_rt::test]
469    async fn test_try_body_json_error() {
470        let app =
471            init_service(App::new().service(web::resource("/people").route(
472                web::post().to(|person: web::Json<Person>| HttpResponse::Ok().json(person)),
473            )))
474            .await;
475
476        // Use a number for id to cause a deserialization error.
477        let payload = r#"{"id":12345,"name":"User name"}"#.as_bytes();
478
479        let res = TestRequest::post()
480            .uri("/people")
481            .insert_header((header::CONTENT_TYPE, "application/json"))
482            .set_payload(payload)
483            .send_request(&app)
484            .await;
485
486        let result: Result<Person, Box<dyn StdError>> = try_read_body_json(res).await;
487        assert!(result.is_err());
488    }
489
490    #[actix_rt::test]
491    async fn test_request_response_form() {
492        let app =
493            init_service(App::new().service(web::resource("/people").route(
494                web::post().to(|person: web::Form<Person>| HttpResponse::Ok().json(person)),
495            )))
496            .await;
497
498        let payload = Person {
499            id: "12345".to_string(),
500            name: "User name".to_string(),
501        };
502
503        let req = TestRequest::post()
504            .uri("/people")
505            .set_form(&payload)
506            .to_request();
507
508        assert_eq!(req.content_type(), "application/x-www-form-urlencoded");
509
510        let result: Person = call_and_read_body_json(&app, req).await;
511        assert_eq!(&result.id, "12345");
512        assert_eq!(&result.name, "User name");
513    }
514
515    #[actix_rt::test]
516    async fn test_response() {
517        let app = init_service(
518            App::new().service(
519                web::resource("/index.html")
520                    .route(web::post().to(|| HttpResponse::Ok().body("welcome!"))),
521            ),
522        )
523        .await;
524
525        let req = TestRequest::post()
526            .uri("/index.html")
527            .insert_header((header::CONTENT_TYPE, "application/json"))
528            .to_request();
529
530        let result = call_and_read_body(&app, req).await;
531        assert_eq!(result, Bytes::from_static(b"welcome!"));
532    }
533
534    #[actix_rt::test]
535    async fn test_request_response_json() {
536        let app =
537            init_service(App::new().service(web::resource("/people").route(
538                web::post().to(|person: web::Json<Person>| HttpResponse::Ok().json(person)),
539            )))
540            .await;
541
542        let payload = Person {
543            id: "12345".to_string(),
544            name: "User name".to_string(),
545        };
546
547        let req = TestRequest::post()
548            .uri("/people")
549            .set_json(&payload)
550            .to_request();
551
552        assert_eq!(req.content_type(), "application/json");
553
554        let result: Person = call_and_read_body_json(&app, req).await;
555        assert_eq!(&result.id, "12345");
556        assert_eq!(&result.name, "User name");
557    }
558
559    #[actix_rt::test]
560    #[allow(dead_code)]
561    async fn return_opaque_types() {
562        fn test_app() -> App<
563            impl ServiceFactory<
564                ServiceRequest,
565                Config = (),
566                Response = ServiceResponse<impl MessageBody>,
567                Error = crate::Error,
568                InitError = (),
569            >,
570        > {
571            App::new().service(
572                web::resource("/people").route(
573                    web::post().to(|person: web::Json<Person>| HttpResponse::Ok().json(person)),
574                ),
575            )
576        }
577
578        async fn test_service(
579        ) -> impl Service<Request, Response = ServiceResponse<impl MessageBody>, Error = crate::Error>
580        {
581            init_service(test_app()).await
582        }
583
584        async fn compile_test(mut req: Vec<Request>) {
585            let svc = test_service().await;
586            call_service(&svc, req.pop().unwrap()).await;
587            call_and_read_body(&svc, req.pop().unwrap()).await;
588            read_body(call_service(&svc, req.pop().unwrap()).await).await;
589            let _: String = call_and_read_body_json(&svc, req.pop().unwrap()).await;
590            let _: String = read_body_json(call_service(&svc, req.pop().unwrap()).await).await;
591        }
592    }
593}