Skip to main content

axum_test/
test_server_builder.rs

1use crate::TestServer;
2use crate::TestServerConfig;
3use crate::Transport;
4use crate::internals::ErrorMessage;
5use crate::transport_layer::IntoTransportLayer;
6use anyhow::Result;
7use std::net::IpAddr;
8use std::net::TcpListener as StdTcpListener;
9
10/// A builder for [`TestServer`]. Inside is a [`TestServerConfig`],
11/// configured by each method, and then turn into a server by [`TestServerBuilder::build`].
12///
13/// The recommended way to make instances is to call [`TestServer::builder`].
14///
15/// # Creating a [`TestServer`]
16///
17/// ```rust
18/// # async fn test() -> Result<(), Box<dyn ::std::error::Error>> {
19/// #
20/// use axum::Router;
21/// use axum_test::TestServer;
22///
23/// let my_app = Router::new();
24/// let server = TestServer::builder()
25///     .save_cookies()
26///     .default_content_type(&"application/json")
27///     .build(my_app);
28/// #
29/// # Ok(())
30/// # }
31/// ```
32///
33#[derive(Default, Debug, Clone, PartialEq)]
34pub struct TestServerBuilder {
35    config: TestServerConfig,
36}
37
38impl TestServerBuilder {
39    /// Creates a default `TestServerBuilder`.
40    pub fn new() -> Self {
41        Default::default()
42    }
43
44    pub fn from_config(config: TestServerConfig) -> Self {
45        Self { config }
46    }
47
48    pub fn http_transport(self) -> Self {
49        self.transport(Transport::HttpRandomPort)
50    }
51
52    pub fn http_transport_with_ip_port(self, ip: Option<IpAddr>, port: Option<u16>) -> Self {
53        self.transport(Transport::HttpIpPort { ip, port })
54    }
55
56    pub fn http_transport_with_tcp_listener(self, tcp_listener: StdTcpListener) -> Self {
57        self.transport(Transport::HttpTcpListner { tcp_listener })
58    }
59
60    pub fn mock_transport(self) -> Self {
61        self.transport(Transport::MockHttp)
62    }
63
64    pub fn transport(mut self, transport: Transport) -> Self {
65        self.config.transport = Some(transport);
66        self
67    }
68
69    pub fn save_cookies(mut self) -> Self {
70        self.config.save_cookies = true;
71        self
72    }
73
74    pub fn do_not_save_cookies(mut self) -> Self {
75        self.config.save_cookies = false;
76        self
77    }
78
79    pub fn default_content_type(mut self, content_type: &str) -> Self {
80        self.config.default_content_type = Some(content_type.to_string());
81        self
82    }
83
84    pub fn expect_success_by_default(mut self) -> Self {
85        self.config.expect_success_by_default = true;
86        self
87    }
88
89    pub fn restrict_requests_with_http_scheme(mut self) -> Self {
90        self.config.restrict_requests_with_http_scheme = true;
91        self
92    }
93
94    /// Creates a new [`TestServer`], running the application given,
95    /// and with all settings from this `TestServerBuilder` applied.
96    ///
97    /// ```rust
98    /// use axum::Router;
99    /// use axum_test::TestServer;
100    ///
101    /// let app = Router::new();
102    /// let server = TestServer::builder()
103    ///     .save_cookies()
104    ///     .default_content_type(&"application/json")
105    ///     .build(app);
106    /// ```
107    ///
108    /// This is the equivalent to building [`TestServerConfig`] yourself,
109    /// and calling [`TestServer::new_with_config`].
110    ///
111    /// Note: this will panic if the [`TestServer`] cannot be built.
112    /// To catch the error use [`TestServerBuilder::try_build`].
113    pub fn build<A>(self, app: A) -> TestServer
114    where
115        A: IntoTransportLayer,
116    {
117        self.try_build(app)
118            .error_message("Failed to build TestServer")
119    }
120
121    /// Attempts to build a [`TestServer`] from this builder,
122    /// and returns an error if this fails.
123    pub fn try_build<A>(self, app: A) -> Result<TestServer>
124    where
125        A: IntoTransportLayer,
126    {
127        self.into_config().try_build(app)
128    }
129
130    /// For turning this into a [`TestServerConfig`] object,
131    /// with can be passed to [`TestServer::new_with_config`].
132    ///
133    /// ```rust
134    /// # async fn test() -> Result<(), Box<dyn ::std::error::Error>> {
135    /// #
136    /// use axum::Router;
137    /// use axum_test::TestServer;
138    ///
139    /// let my_app = Router::new();
140    /// let config = TestServer::builder()
141    ///     .save_cookies()
142    ///     .default_content_type(&"application/json")
143    ///     .into_config();
144    ///
145    /// // Build the Test Server
146    /// let server = TestServer::new_with_config(my_app, config);
147    /// #
148    /// # Ok(())
149    /// # }
150    /// ```
151    pub fn into_config(self) -> TestServerConfig {
152        self.config
153    }
154}
155
156impl From<TestServerConfig> for TestServerBuilder {
157    fn from(config: TestServerConfig) -> Self {
158        TestServerBuilder::from_config(config)
159    }
160}
161
162#[cfg(test)]
163mod test_build {
164    use super::*;
165    use std::net::Ipv4Addr;
166
167    #[test]
168    fn it_should_build_default_config_by_default() {
169        let config = TestServer::builder().into_config();
170        let expected = TestServerConfig::default();
171
172        assert_eq!(config, expected);
173    }
174
175    #[test]
176    fn it_should_save_cookies_when_set() {
177        let config = TestServer::builder().save_cookies().into_config();
178
179        assert_eq!(config.save_cookies, true);
180    }
181
182    #[test]
183    fn it_should_not_save_cookies_when_set() {
184        let config = TestServer::builder().do_not_save_cookies().into_config();
185
186        assert_eq!(config.save_cookies, false);
187    }
188
189    #[test]
190    fn it_should_mock_transport_when_set() {
191        let config = TestServer::builder().mock_transport().into_config();
192
193        assert_eq!(config.transport, Some(Transport::MockHttp));
194    }
195
196    #[test]
197    fn it_should_use_random_http_transport_when_set() {
198        let config = TestServer::builder().http_transport().into_config();
199
200        assert_eq!(config.transport, Some(Transport::HttpRandomPort));
201    }
202
203    #[test]
204    fn it_should_use_http_transport_with_ip_port_when_set() {
205        let config = TestServer::builder()
206            .http_transport_with_ip_port(Some(IpAddr::V4(Ipv4Addr::new(123, 4, 5, 6))), Some(987))
207            .into_config();
208
209        assert_eq!(
210            config.transport,
211            Some(Transport::HttpIpPort {
212                ip: Some(IpAddr::V4(Ipv4Addr::new(123, 4, 5, 6))),
213                port: Some(987),
214            })
215        );
216    }
217
218    #[test]
219    fn it_should_set_default_content_type_when_set() {
220        let config = TestServer::builder()
221            .default_content_type("text/csv")
222            .into_config();
223
224        assert_eq!(config.default_content_type, Some("text/csv".to_string()));
225    }
226
227    #[test]
228    fn it_should_set_expect_success_by_default_when_set() {
229        let config = TestServer::builder()
230            .expect_success_by_default()
231            .into_config();
232
233        assert_eq!(config.expect_success_by_default, true);
234    }
235
236    #[test]
237    fn it_should_set_restrict_requests_with_http_scheme_when_set() {
238        let config = TestServer::builder()
239            .restrict_requests_with_http_scheme()
240            .into_config();
241
242        assert_eq!(config.restrict_requests_with_http_scheme, true);
243    }
244}