1use std::{borrow::Cow, net::SocketAddr, rc::Rc};
2
3use actix_http::{test::TestRequest as HttpTestRequest, Request};
4use serde::Serialize;
5
6#[cfg(feature = "cookies")]
7use crate::cookie::{Cookie, CookieJar};
8use crate::{
9 app_service::AppInitServiceState,
10 config::AppConfig,
11 data::Data,
12 dev::{Extensions, Path, Payload, ResourceDef, Service, Url},
13 http::{
14 header::{ContentType, TryIntoHeaderPair},
15 Method, Uri, Version,
16 },
17 rmap::ResourceMap,
18 service::{ServiceRequest, ServiceResponse},
19 test,
20 web::Bytes,
21 HttpRequest, HttpResponse,
22};
23
24pub struct TestRequest {
62 req: HttpTestRequest,
63 rmap: ResourceMap,
64 config: AppConfig,
65 path: Path<Url>,
66 peer_addr: Option<SocketAddr>,
67 app_data: Extensions,
68 #[cfg(feature = "cookies")]
69 cookies: CookieJar,
70}
71
72impl Default for TestRequest {
73 fn default() -> TestRequest {
74 TestRequest {
75 req: HttpTestRequest::default(),
76 rmap: ResourceMap::new(ResourceDef::new("")),
77 config: AppConfig::default(),
78 path: Path::new(Url::new(Uri::default())),
79 peer_addr: None,
80 app_data: Extensions::new(),
81 #[cfg(feature = "cookies")]
82 cookies: CookieJar::new(),
83 }
84 }
85}
86
87#[allow(clippy::wrong_self_convention)]
88impl TestRequest {
89 pub fn with_uri(uri: &str) -> TestRequest {
91 TestRequest::default().uri(uri)
92 }
93
94 pub fn get() -> TestRequest {
96 TestRequest::default().method(Method::GET)
97 }
98
99 pub fn post() -> TestRequest {
101 TestRequest::default().method(Method::POST)
102 }
103
104 pub fn put() -> TestRequest {
106 TestRequest::default().method(Method::PUT)
107 }
108
109 pub fn patch() -> TestRequest {
111 TestRequest::default().method(Method::PATCH)
112 }
113
114 pub fn delete() -> TestRequest {
116 TestRequest::default().method(Method::DELETE)
117 }
118
119 pub fn version(mut self, ver: Version) -> Self {
121 self.req.version(ver);
122 self
123 }
124
125 pub fn method(mut self, meth: Method) -> Self {
127 self.req.method(meth);
128 self
129 }
130
131 pub fn uri(mut self, path: &str) -> Self {
133 self.req.uri(path);
134 self
135 }
136
137 pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self {
139 self.req.insert_header(header);
140 self
141 }
142
143 pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
145 self.req.append_header(header);
146 self
147 }
148
149 #[cfg(feature = "cookies")]
151 pub fn cookie(mut self, cookie: Cookie<'_>) -> Self {
152 self.cookies.add(cookie.into_owned());
153 self
154 }
155
156 pub fn param(
167 mut self,
168 name: impl Into<Cow<'static, str>>,
169 value: impl Into<Cow<'static, str>>,
170 ) -> Self {
171 self.path.add_static(name, value);
172 self
173 }
174
175 pub fn peer_addr(mut self, addr: SocketAddr) -> Self {
177 self.peer_addr = Some(addr);
178 self
179 }
180
181 pub fn set_payload(mut self, data: impl Into<Bytes>) -> Self {
183 self.req.set_payload(data);
184 self
185 }
186
187 pub fn set_form(mut self, data: impl Serialize) -> Self {
191 let bytes = serde_urlencoded::to_string(&data)
192 .expect("Failed to serialize test data as a urlencoded form");
193 self.req.set_payload(bytes);
194 self.req.insert_header(ContentType::form_url_encoded());
195 self
196 }
197
198 pub fn set_json(mut self, data: impl Serialize) -> Self {
202 let bytes = serde_json::to_string(&data).expect("Failed to serialize test data to json");
203 self.req.set_payload(bytes);
204 self.req.insert_header(ContentType::json());
205 self
206 }
207
208 pub fn app_data<T: 'static>(mut self, data: T) -> Self {
212 self.app_data.insert(data);
213 self
214 }
215
216 #[doc(hidden)]
220 pub fn data<T: 'static>(mut self, data: T) -> Self {
221 self.app_data.insert(Data::new(data));
222 self
223 }
224
225 #[cfg(test)]
227 pub(crate) fn rmap(mut self, rmap: ResourceMap) -> Self {
228 self.rmap = rmap;
229 self
230 }
231
232 fn finish(&mut self) -> Request {
236 #[allow(unused_mut)]
238 let mut req = self.req.finish();
239
240 #[cfg(feature = "cookies")]
241 {
242 use actix_http::header::{HeaderValue, COOKIE};
243
244 let cookie: String = self
245 .cookies
246 .delta()
247 .map(|c| c.stripped().encoded().to_string())
249 .collect::<Vec<_>>()
250 .join("; ");
251
252 if !cookie.is_empty() {
253 req.headers_mut()
254 .insert(COOKIE, HeaderValue::from_str(&cookie).unwrap());
255 }
256 }
257
258 req
259 }
260
261 pub fn to_request(mut self) -> Request {
263 let mut req = self.finish();
264 req.head_mut().peer_addr = self.peer_addr;
265 req
266 }
267
268 pub fn to_srv_request(mut self) -> ServiceRequest {
270 let (mut head, payload) = self.finish().into_parts();
271 head.peer_addr = self.peer_addr;
272 self.path.get_mut().update(&head.uri);
273
274 let app_state =
275 AppInitServiceState::new_without_pool(Rc::new(self.rmap), self.config.clone());
276
277 ServiceRequest::new(
278 HttpRequest::new(
279 self.path,
280 head,
281 app_state,
282 Rc::new(self.app_data),
283 None,
284 Default::default(),
285 ),
286 payload,
287 )
288 }
289
290 pub fn to_srv_response<B>(self, res: HttpResponse<B>) -> ServiceResponse<B> {
292 self.to_srv_request().into_response(res)
293 }
294
295 pub fn to_http_request(mut self) -> HttpRequest {
297 let (mut head, _) = self.finish().into_parts();
298 head.peer_addr = self.peer_addr;
299 self.path.get_mut().update(&head.uri);
300
301 let app_state =
302 AppInitServiceState::new_without_pool(Rc::new(self.rmap), self.config.clone());
303
304 HttpRequest::new(
305 self.path,
306 head,
307 app_state,
308 Rc::new(self.app_data),
309 None,
310 Default::default(),
311 )
312 }
313
314 pub fn to_http_parts(mut self) -> (HttpRequest, Payload) {
316 let (mut head, payload) = self.finish().into_parts();
317 head.peer_addr = self.peer_addr;
318 self.path.get_mut().update(&head.uri);
319
320 let app_state =
321 AppInitServiceState::new_without_pool(Rc::new(self.rmap), self.config.clone());
322
323 let req = HttpRequest::new(
324 self.path,
325 head,
326 app_state,
327 Rc::new(self.app_data),
328 None,
329 Default::default(),
330 );
331
332 (req, payload)
333 }
334
335 pub async fn send_request<S, B, E>(self, app: &S) -> S::Response
337 where
338 S: Service<Request, Response = ServiceResponse<B>, Error = E>,
339 E: std::fmt::Debug,
340 {
341 let req = self.to_request();
342 test::call_service(app, req).await
343 }
344
345 #[cfg(test)]
346 pub fn set_server_hostname(&mut self, host: &str) {
347 self.config.set_host(host)
348 }
349}
350
351#[cfg(test)]
352mod tests {
353 use std::{sync::Arc, time::SystemTime};
354
355 use super::*;
356 use crate::{http::header, test::init_service, web, App, Error, Responder};
357
358 #[actix_rt::test]
359 async fn test_request_app_data_is_dropped() {
360 fn assert_dropped(make_request: impl FnOnce(TestRequest)) {
361 let data = Arc::new(());
362 let weak = Arc::downgrade(&data);
363 make_request(TestRequest::default().app_data(Data::new(data)));
364 assert!(weak.upgrade().is_none());
365 }
366
367 assert_dropped(|req| drop(req.to_srv_request()));
368 assert_dropped(|req| drop(req.to_http_request()));
369 assert_dropped(|req| drop(req.to_http_parts()));
370 }
371
372 #[actix_rt::test]
373 async fn test_basics() {
374 let req = TestRequest::default()
375 .version(Version::HTTP_2)
376 .insert_header(header::ContentType::json())
377 .insert_header(header::Date(SystemTime::now().into()))
378 .param("test", "123")
379 .data(10u32)
380 .app_data(20u64)
381 .peer_addr("127.0.0.1:8081".parse().unwrap())
382 .to_http_request();
383 assert!(req.headers().contains_key(header::CONTENT_TYPE));
384 assert!(req.headers().contains_key(header::DATE));
385 assert_eq!(
386 req.head().peer_addr,
387 Some("127.0.0.1:8081".parse().unwrap())
388 );
389 assert_eq!(&req.match_info()["test"], "123");
390 assert_eq!(req.version(), Version::HTTP_2);
391 let data = req.app_data::<Data<u32>>().unwrap();
392 assert!(req.app_data::<Data<u64>>().is_none());
393 assert_eq!(*data.get_ref(), 10);
394
395 assert!(req.app_data::<u32>().is_none());
396 let data = req.app_data::<u64>().unwrap();
397 assert_eq!(*data, 20);
398 }
399
400 #[actix_rt::test]
401 async fn test_send_request() {
402 let app = init_service(
403 App::new().service(
404 web::resource("/index.html")
405 .route(web::get().to(|| HttpResponse::Ok().body("welcome!"))),
406 ),
407 )
408 .await;
409
410 let resp = TestRequest::get()
411 .uri("/index.html")
412 .send_request(&app)
413 .await;
414
415 let result = test::read_body(resp).await;
416 assert_eq!(result, Bytes::from_static(b"welcome!"));
417 }
418
419 #[actix_rt::test]
420 async fn test_async_with_block() {
421 async fn async_with_block() -> Result<HttpResponse, Error> {
422 let res = web::block(move || Some(4usize).ok_or("wrong")).await;
423
424 match res {
425 Ok(value) => Ok(HttpResponse::Ok()
426 .content_type("text/plain")
427 .body(format!("Async with block value: {:?}", value))),
428 Err(_) => panic!("Unexpected"),
429 }
430 }
431
432 let app =
433 init_service(App::new().service(web::resource("/index.html").to(async_with_block)))
434 .await;
435
436 let req = TestRequest::post().uri("/index.html").to_request();
437 let res = app.call(req).await.unwrap();
438 assert!(res.status().is_success());
439 }
440
441 #[allow(deprecated)]
443 #[actix_rt::test]
444 async fn test_server_data() {
445 async fn handler(data: web::Data<usize>) -> impl Responder {
446 assert_eq!(**data, 10);
447 HttpResponse::Ok()
448 }
449
450 let app = init_service(
451 App::new()
452 .data(10usize)
453 .service(web::resource("/index.html").to(handler)),
454 )
455 .await;
456
457 let req = TestRequest::post().uri("/index.html").to_request();
458 let res = app.call(req).await.unwrap();
459 assert!(res.status().is_success());
460 }
461}