Skip to main content

trillium_testing/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![forbid(unsafe_code)]
3#![deny(
4    clippy::dbg_macro,
5    missing_copy_implementations,
6    rustdoc::missing_crate_level_docs,
7    missing_debug_implementations,
8    missing_docs,
9    nonstandard_style,
10    unused_qualifications
11)]
12
13//! Testing utilities for trillium applications.
14//!
15//! This crate is intended to be used as a development dependency.
16//!
17//! ```
18//! # trillium_testing::block_on(async {
19//! use trillium::{Conn, Status, conn_try};
20//! use trillium_testing::TestServer;
21//! async fn handler(mut conn: Conn) -> Conn {
22//!     let request_body = conn_try!(conn.request_body_string().await, conn);
23//!     conn.with_body(format!("request body was: {}", request_body))
24//!         .with_status(418)
25//!         .with_response_header("request-id", "special-request")
26//! }
27//!
28//! let app = TestServer::new(handler).await;
29//! app.post("/")
30//!     .with_body("hello trillium!")
31//!     .await
32//!     .assert_status(Status::ImATeapot)
33//!     .assert_body("request body was: hello trillium!")
34//!     .assert_headers([("request-id", "special-request"), ("content-length", "33")]);
35//! # });
36//! ```
37//!
38//! ## Features
39//!
40//! To use the same runtime as your application, enable a runtime feature for **trillium testing**.
41//! Without a runtime feature enabled, trillium testing will approximate a runtime through spawning
42//! a thread per task and blocking on a future. This is fine for simple testing, but you probably
43//! want to enable a server feature.
44//!
45//! ### Tokio:
46//! ```toml
47//! [dev-dependencies]
48//! # ...
49//! trillium-testing = { version = "...", features = ["tokio"] }
50//! ```
51//!
52//! ### Async-std:
53//! ```toml
54//! [dev-dependencies]
55//! # ...
56//! trillium-testing = { version = "...", features = ["async-std"] }
57//! ```
58//!
59//! ### Smol:
60//! ```toml
61//! [dev-dependencies]
62//! # ...
63//! trillium-testing = { version = "...", features = ["smol"] }
64//! ```
65
66#[cfg(test)]
67#[doc = include_str!("../README.md")]
68mod readme {}
69
70mod assertions;
71
72mod test_transport;
73use std::{future::Future, process::Termination, sync::Arc};
74pub use test_transport::TestTransport;
75
76mod test_conn;
77pub use test_conn::TestConn;
78
79pub mod methods;
80pub mod prelude {
81    //! useful stuff for testing trillium apps
82    pub use crate::{
83        assert_body, assert_body_contains, assert_headers, assert_not_handled, assert_ok,
84        assert_response, assert_status, block_on, connector, init, methods::*,
85    };
86    pub use trillium::{Conn, Method, Status};
87}
88
89use trillium::{Handler, Info};
90pub use trillium::{Method, Status};
91use trillium_http::ServerConfig;
92pub use url::Url;
93
94/// runs the future to completion on the current thread
95pub fn block_on<Fut: Future>(fut: Fut) -> Fut::Output {
96    runtime().block_on(fut)
97}
98
99/// initialize a handler
100pub async fn init(handler: &mut impl Handler) -> Arc<ServerConfig> {
101    let mut info = Info::from(ServerConfig::default());
102    info.insert_state(runtime());
103    info.insert_state(runtime().into());
104    handler.init(&mut info).await;
105    Arc::new(info.into())
106}
107
108// these exports are used by macros
109pub use futures_lite::{self, AsyncRead, AsyncReadExt, AsyncWrite, Stream};
110
111mod server_connector;
112pub use server_connector::{ServerConnector, connector};
113use trillium_server_common::Config;
114pub use trillium_server_common::{
115    ArcedConnector, Connector, Runtime, RuntimeTrait, Server, ServerHandle,
116};
117
118cfg_if::cfg_if! {
119    if #[cfg(feature = "smol")] {
120        /// runtime server config
121        pub fn config() -> Config<impl Server, ()> {
122            trillium_smol::config()
123        }
124
125        /// runtime client config
126        pub fn client_config() -> impl Connector {
127            trillium_smol::ClientConfig::default()
128        }
129        /// smol runtime
130        pub fn runtime() -> impl RuntimeTrait {
131            trillium_smol::SmolRuntime::default()
132        }
133    } else if #[cfg(feature = "async-std")] {
134        /// runtime server config
135        pub fn config() -> Config<impl Server, ()> {
136            trillium_async_std::config()
137        }
138        /// runtime client config
139        pub fn client_config() -> impl Connector {
140            trillium_async_std::ClientConfig::default()
141        }
142        /// async std runtime
143        pub fn runtime() -> impl RuntimeTrait {
144            trillium_async_std::AsyncStdRuntime::default()
145        }
146    } else if #[cfg(feature = "tokio")] {
147        /// runtime server config
148        pub fn config() -> Config<impl Server, ()> {
149            trillium_tokio::config()
150        }
151
152        /// tokio client config
153        pub fn client_config() -> impl Connector {
154            trillium_tokio::ClientConfig::default()
155        }
156
157        /// tokio runtime
158        pub fn runtime() -> impl RuntimeTrait {
159            trillium_tokio::TokioRuntime::default()
160        }
161   } else {
162        /// runtime server config
163        pub fn config() -> Config<impl Server, ()> {
164            Config::<RuntimelessServer, ()>::new()
165        }
166
167        /// generic client config
168        pub fn client_config() -> impl Connector {
169            RuntimelessClientConfig::default()
170        }
171
172       /// generic runtime
173       pub fn runtime() -> impl RuntimeTrait {
174           RuntimelessRuntime::default()
175       }
176   }
177}
178
179mod with_server;
180pub use with_server::{with_server, with_transport};
181
182mod test_server;
183pub use test_server::{ConnTest, TestServer};
184
185mod runtimeless;
186pub use runtimeless::{RuntimelessClientConfig, RuntimelessRuntime, RuntimelessServer};
187
188/// a sponge Result
189pub type TestResult = Result<(), Box<dyn std::error::Error>>;
190
191/// a test harness for use with [`test_harness`]
192#[track_caller]
193pub fn harness<F, Fut, Output>(test: F) -> Output
194where
195    F: FnOnce() -> Fut,
196    Fut: Future<Output = Output>,
197    Output: Termination,
198{
199    let _ = env_logger::builder().is_test(true).try_init();
200    block_on(test())
201}
202
203/// a harness that includes the runtime
204#[track_caller]
205pub fn with_runtime<F, Fut, Output>(test: F) -> Output
206where
207    F: FnOnce(Runtime) -> Fut,
208    Fut: Future<Output = Output>,
209    Output: Termination,
210{
211    let runtime = runtime();
212    runtime.clone().block_on(test(runtime.into()))
213}
214
215pub use test_harness::test;
216
217mod http_test;
218#[doc(hidden)]
219pub use http_test::HttpTest;
220
221#[cfg(all(feature = "serde_json", feature = "sonic-rs"))]
222compile_error!("cargo features \"serde_json\" and \"sonic-rs\" are mutually exclusive");
223
224#[cfg(feature = "serde_json")]
225#[cfg_attr(docsrs, doc(cfg(feature = "serde_json")))]
226pub use serde_json::{Value, from_str as from_json_str, json, to_string as to_json_string};
227#[cfg(feature = "sonic-rs")]
228#[cfg_attr(docsrs, doc(cfg(feature = "sonic-rs")))]
229pub use sonic_rs::{Value, from_str as from_json_str, json, to_string as to_json_string};