mockito/
lib.rs

1#![warn(missing_docs)]
2#![doc(
3    html_logo_url = "https://raw.githubusercontent.com/lipanski/mockito/master/docs/logo-black-100.png"
4)]
5
6//!
7//! Mockito is a library for **generating and delivering HTTP mocks** in Rust. You can use it for integration testing
8//! or offline work. Mockito runs a local pool of HTTP servers which create, deliver and remove the mocks.
9//!
10//! # Features
11//!
12//! - Supports HTTP1/2
13//! - Runs your tests in parallel
14//! - Comes with a wide range of request matchers (Regex, JSON, query parameters etc.)
15//! - Checks that a mock was called (spy)
16//! - Mocks multiple hosts at the same time
17//! - Exposes sync and async interfaces
18//! - Prints out a colored diff of the last unmatched request in case of errors
19//! - Simple, intuitive API
20//! - An awesome logo
21//!
22//! # Getting Started
23//!
24//! Add `mockito` to your `Cargo.toml` and start mocking:
25//!
26//! ```
27//! #[cfg(test)]
28//! mod tests {
29//!   #[test]
30//!   fn test_something() {
31//!     // Request a new server from the pool
32//!     let mut server = mockito::Server::new();
33//!
34//!     // Use one of these addresses to configure your client
35//!     let host = server.host_with_port();
36//!     let url = server.url();
37//!
38//!     // Create a mock
39//!     let mock = server.mock("GET", "/hello")
40//!       .with_status(201)
41//!       .with_header("content-type", "text/plain")
42//!       .with_header("x-api-key", "1234")
43//!       .with_body("world")
44//!       .create();
45//!
46//!     // Any calls to GET /hello beyond this line will respond with 201, the
47//!     // `content-type: text/plain` header and the body "world".
48//!
49//!     // You can use `Mock::assert` to verify that your mock was called
50//!     // mock.assert();
51//!   }
52//! }
53//! ```
54//!
55//! If [`Mock::assert`] fails, a colored diff of the last unmatched request is displayed:
56//!
57//! ![colored-diff.png](https://raw.githubusercontent.com/lipanski/mockito/master/docs/colored-diff.png)
58//!
59//! Use **matchers** to handle requests to the same endpoint in a different way:
60//!
61//! ```
62//! #[cfg(test)]
63//! mod tests {
64//!   #[test]
65//!   fn test_something() {
66//!     let mut server = mockito::Server::new();
67//!
68//!     server.mock("GET", "/greetings")
69//!       .match_header("content-type", "application/json")
70//!       .match_body(mockito::Matcher::PartialJsonString(
71//!           "{\"greeting\": \"hello\"}".to_string(),
72//!       ))
73//!       .with_body("hello json")
74//!       .create();
75//!
76//!     server.mock("GET", "/greetings")
77//!       .match_header("content-type", "application/text")
78//!       .match_body(mockito::Matcher::Regex("greeting=hello".to_string()))
79//!       .with_body("hello text")
80//!       .create();
81//!   }
82//! }
83//! ```
84//!
85//! Start **multiple servers** to simulate requests to different hosts:
86//!
87//! ```
88//! #[cfg(test)]
89//! mod tests {
90//!   #[test]
91//!   fn test_something() {
92//!     let mut twitter = mockito::Server::new();
93//!     let mut github = mockito::Server::new();
94//!
95//!     // These mocks will be available at `twitter.url()`
96//!     let twitter_mock = twitter.mock("GET", "/api").create();
97//!
98//!     // These mocks will be available at `github.url()`
99//!     let github_mock = github.mock("GET", "/api").create();
100//!   }
101//! }
102//! ```
103//!
104//! Write **async** tests (make sure to use the `_async` methods!):
105//!
106//! ```
107//! #[cfg(test)]
108//! mod tests {
109//! # use mockito::Server;
110//!   #[tokio::test]
111//!   async fn test_something() {
112//!     let mut server = Server::new_async().await;
113//!     let m1 = server.mock("GET", "/a").with_body("aaa").create_async().await;
114//!     let m2 = server.mock("GET", "/b").with_body("bbb").create_async().await;
115//!
116//!     let (m1, m2) = futures::join!(m1, m2);
117//!
118//!     // You can use `Mock::assert_async` to verify that your mock was called
119//!     // m1.assert_async().await;
120//!     // m2.assert_async().await;
121//!   }
122//! }
123//! ```
124//!
125//! Start a **stand-alone server** on a dedicated port:
126//!
127//! ```
128//! fn main() {
129//!     let opts = mockito::ServerOpts {
130//!         host: "0.0.0.0",
131//!         port: 1234,
132//!         ..Default::default()
133//!     };
134//!     let mut server = mockito::Server::new_with_opts(opts);
135//!
136//!     let _m = server.mock("GET", "/").with_body("hello world").create();
137//!
138//!     // loop {}
139//! }
140//! ```
141//!
142//! # Lifetime
143//!
144//! A mock is available only throughout the lifetime of the server. Once the server goes
145//! out of scope, all mocks defined on that server are removed:
146//!
147//! ```
148//! let address;
149//!
150//! {
151//!     let mut s = mockito::Server::new();
152//!     address = s.host_with_port();
153//!
154//!     s.mock("GET", "/").with_body("hi").create();
155//!
156//!     // Requests to `address` will be responded with "hi" til here
157//! }
158//!
159//! // Requests to `address` will fail as of this point
160//! ```
161//!
162//! You can remove individual mocks earlier by calling [`Mock::remove`].
163//!
164//! # Async
165//!
166//! Mockito comes with both a sync and an async interface.
167//!
168//! In order to write async tests, you'll need to use the `*_async` methods:
169//!
170//! - [`Server::new_async`]
171//! - [`Server::new_with_opts_async`]
172//! - [`Mock::create_async`]
173//! - [`Mock::assert_async`]
174//! - [`Mock::matched_async`]
175//! - [`Mock::remove_async`]
176//!
177//! ...otherwise your tests will not compile, and you'll see the following error:
178//!
179//! ```text
180//! Cannot block the current thread from within a runtime.
181//! This happens because a function attempted to block the current thread while the thread is being used to drive asynchronous tasks.
182//! ```
183//!
184//! # Configuring the server
185//!
186//! When calling [`Server::new()`], a mock server with default options is returned from the server
187//! pool. This should suffice for most use cases.
188//!
189//! If you'd like to bypass the server pool or configure the server in a different
190//! way, you can use [`Server::new_with_opts`]. The following **options** are available:
191//!
192//! - `host`: allows setting the host (defaults to `127.0.0.1`)
193//! - `port`: allows setting the port (defaults to a randomly assigned free port)
194//! - `assert_on_drop`: automatically call [`Mock::assert()`] before dropping a mock (defaults to `false`)
195//!
196//! ```
197//! let opts = mockito::ServerOpts { assert_on_drop: true, ..Default::default() };
198//! let server = mockito::Server::new_with_opts(opts);
199//! ```
200//!
201//! # Matchers
202//!
203//! Mockito can match your request by method, path, query, headers or body.
204//!
205//! Various matchers are provided by the [`Matcher`] type: exact (string, binary, JSON), partial (regular expressions,
206//! JSON), any or missing. The following guide will walk you through the most common matchers. Check the
207//! [`Matcher`] documentation for all the rest.
208//!
209//! # Matching by path and query
210//!
211//! By default, the request path and query is compared by its exact value:
212//!
213//! ## Example
214//!
215//! ```
216//! let mut s = mockito::Server::new();
217//!
218//! // Matches only calls to GET /hello
219//! s.mock("GET", "/hello").create();
220//!
221//! // Matches only calls to GET /hello?world=1
222//! s.mock("GET", "/hello?world=1").create();
223//! ```
224//!
225//! You can also match the path partially, by using a regular expression:
226//!
227//! ## Example
228//!
229//! ```
230//! let mut s = mockito::Server::new();
231//!
232//! // Will match calls to GET /hello/1 and GET /hello/2
233//! s.mock("GET",
234//!     mockito::Matcher::Regex(r"^/hello/(1|2)$".to_string())
235//!   ).create();
236//! ```
237//!
238//! Or you can catch all requests, by using the [`Matcher::Any`] variant:
239//!
240//! ## Example
241//!
242//! ```
243//! let mut s = mockito::Server::new();
244//!
245//! // Will match any GET request
246//! s.mock("GET", mockito::Matcher::Any).create();
247//! ```
248//!
249//! # Matching by query
250//!
251//! You can match the query part by using the [`Mock::match_query`] function together with the various matchers,
252//! most notably [`Matcher::UrlEncoded`]:
253//!
254//! ## Example
255//!
256//! ```
257//! let mut s = mockito::Server::new();
258//!
259//! // This will match requests containing the URL-encoded
260//! // query parameter `greeting=good%20day`
261//! s.mock("GET", "/test")
262//!   .match_query(mockito::Matcher::UrlEncoded("greeting".into(), "good day".into()))
263//!   .create();
264//!
265//! // This will match requests containing the URL-encoded
266//! // query parameters `hello=world` and `greeting=good%20day`
267//! s.mock("GET", "/test")
268//!   .match_query(mockito::Matcher::AllOf(vec![
269//!     mockito::Matcher::UrlEncoded("hello".into(), "world".into()),
270//!     mockito::Matcher::UrlEncoded("greeting".into(), "good day".into())
271//!   ]))
272//!   .create();
273//!
274//! // You can achieve similar results with the regex matcher
275//! s.mock("GET", "/test")
276//!   .match_query(mockito::Matcher::Regex("hello=world".into()))
277//!   .create();
278//! ```
279//!
280//! Note that the key/value arguments for [`Matcher::UrlEncoded`] should be left in plain (unencoded) format.
281//!
282//! You can also specify the query as part of the path argument in a [`mock`](Server::mock) call, in which case an exact
283//! match will be performed:
284//!
285//! ## Example
286//!
287//! ```
288//! let mut s = mockito::Server::new();
289//!
290//! // This will perform a full match against the query part
291//! s.mock("GET", "/test?hello=world").create();
292//! ```
293//!
294//! If you'd like to ignore the query entirely, use the [`Matcher::Any`] variant:
295//!
296//! ## Example
297//!
298//! ```
299//! let mut s = mockito::Server::new();
300//!
301//! // This will match requests to GET /test with any query
302//! s.mock("GET", "/test").match_query(mockito::Matcher::Any).create();
303//! ```
304//!
305//! # Matching by header
306//!
307//! By default, headers are compared by their exact value. The header name letter case is ignored though.
308//!
309//! ## Example
310//!
311//! ```
312//! let mut s = mockito::Server::new();
313//!
314//! s.mock("GET", "/hello")
315//!   .match_header("content-type", "application/json")
316//!   .with_body(r#"{"hello": "world"}"#)
317//!   .create();
318//!
319//! s.mock("GET", "/hello")
320//!   .match_header("content-type", "text/plain")
321//!   .with_body("world")
322//!   .create();
323//!
324//! // JSON requests to GET /hello will respond with JSON, while plain requests
325//! // will respond with text.
326//! ```
327//!
328//! You can also match a header value with a *regular expressions*, by using the [`Matcher::Regex`] matcher:
329//!
330//! ## Example
331//!
332//! ```
333//! let mut s = mockito::Server::new();
334//!
335//! s.mock("GET", "/hello")
336//!   .match_header("content-type", mockito::Matcher::Regex(r".*json.*".to_string()))
337//!   .with_body(r#"{"hello": "world"}"#)
338//!   .create();
339//! ```
340//!
341//! Or you can match a header *only by its field name*, by setting the [`Mock::match_header`] value to [`Matcher::Any`].
342//!
343//! ## Example
344//!
345//! ```
346//! let mut s = mockito::Server::new();
347//!
348//! s.mock("GET", "/hello")
349//!  .match_header("content-type", mockito::Matcher::Any)
350//!  .with_body("something")
351//!  .create();
352//!
353//! // Requests containing any content-type header value will be mocked.
354//! // Requests not containing this header will return `501 Not Implemented`.
355//! ```
356//!
357//! You can mock requests that should be *missing a particular header field*, by setting the [`Mock::match_header`]
358//! value to [`Matcher::Missing`].
359//!
360//! ## Example
361//!
362//! ```
363//! let mut s = mockito::Server::new();
364//!
365//! s.mock("GET", "/hello")
366//!   .match_header("authorization", mockito::Matcher::Missing)
367//!   .with_body("no authorization header")
368//!   .create();
369//!
370//! // Requests without the authorization header will be matched.
371//! // Requests containing the authorization header will return `501 Mock Not Found`.
372//! ```
373//!
374//! # Matching by body
375//!
376//! You can match a request by its body by using the [`Mock::match_body`] method.
377//! By default, the request body is ignored, similar to passing the [`Matcher::Any`] argument to the [`Mock::match_body`] method.
378//!
379//! You can match a body by an exact value:
380//!
381//! ## Example
382//!
383//! ```
384//! let mut s = mockito::Server::new();
385//!
386//! // Will match requests to POST / whenever the request body is "hello"
387//! s.mock("POST", "/").match_body("hello").create();
388//! ```
389//!
390//! Or you can match the body by using a regular expression:
391//!
392//! ## Example
393//!
394//! ```
395//! let mut s = mockito::Server::new();
396//!
397//! // Will match requests to POST / whenever the request body *contains* the word "hello" (e.g. "hello world")
398//! s.mock("POST", "/").match_body(
399//!     mockito::Matcher::Regex("hello".to_string())
400//!   ).create();
401//! ```
402//!
403//! Or you can match the body using a JSON object:
404//!
405//! ## Example
406//!
407//! ```
408//! # extern crate mockito;
409//! #[macro_use]
410//! extern crate serde_json;
411//!
412//! # fn main() {
413//! let mut s = mockito::Server::new();
414//! // Will match requests to POST / whenever the request body matches the json object
415//! s.mock("POST", "/").match_body(mockito::Matcher::Json(json!({"hello": "world"}))).create();
416//! # }
417//! ```
418//!
419//! If `serde_json::json!` is not exposed, you can use [`Matcher::JsonString`] the same way,
420//! but by passing a `String` to the matcher:
421//!
422//! ```
423//! let mut s = mockito::Server::new();
424//!
425//! // Will match requests to POST / whenever the request body matches the json object
426//! s.mock("POST", "/")
427//!     .match_body(
428//!        mockito::Matcher::JsonString(r#"{"hello": "world"}"#.to_string())
429//!     )
430//!     .create();
431//! ```
432//!
433//! # The `AnyOf` matcher
434//!
435//! The [`Matcher::AnyOf`] construct takes a vector of matchers as arguments and will be enabled
436//! if at least one of the provided matchers matches the request.
437//!
438//! ## Example
439//!
440//! ```
441//! let mut s = mockito::Server::new();
442//!
443//! // Will match requests to POST / whenever the request body is either `hello=world` or `{"hello":"world"}`
444//! s.mock("POST", "/")
445//!     .match_body(
446//!         mockito::Matcher::AnyOf(vec![
447//!             mockito::Matcher::Exact("hello=world".to_string()),
448//!             mockito::Matcher::JsonString(r#"{"hello": "world"}"#.to_string()),
449//!         ])
450//!      )
451//!     .create();
452//! ```
453//!
454//! # The `AllOf` matcher
455//!
456//! The [`Matcher::AllOf`] construct takes a vector of matchers as arguments and will be enabled
457//! if all the provided matchers match the request.
458//!
459//! ## Example
460//!
461//! ```
462//! let mut s = mockito::Server::new();
463//!
464//! // Will match requests to POST / whenever the request body contains both `hello` and `world`
465//! s.mock("POST", "/")
466//!     .match_body(
467//!         mockito::Matcher::AllOf(vec![
468//!             mockito::Matcher::Regex("hello".to_string()),
469//!             mockito::Matcher::Regex("world".to_string()),
470//!         ])
471//!      )
472//!     .create();
473//! ```
474//!
475//! # Custom matchers
476//!
477//! If you need a more custom matcher, you can use the [`Mock::match_request`] function, which
478//! takes a closure and exposes the [`Request`] object as an argument. The closure should return
479//! a boolean value.
480//!
481//! ## Example
482//!
483//! ```
484//! use mockito::Matcher;
485//!
486//! let mut s = mockito::Server::new();
487//!
488//! // This will match requests that have the x-test header set
489//! // and contain the word "hello" inside the body
490//! s.mock("GET", "/")
491//!     .match_request(|request| {
492//!         request.has_header("x-test") &&
493//!             request.utf8_lossy_body().unwrap().contains("hello")
494//!     })
495//!     .create();
496//! ```
497//!
498//! # Asserts
499//!
500//! You can use the [`Mock::assert`] method to **assert that a mock was called**. In other words,
501//! `Mock#assert` can validate that your code performed the expected HTTP request.
502//!
503//! By default, the method expects only **one** request to your mock.
504//!
505//! ## Example
506//!
507//! ```no_run
508//! use std::net::TcpStream;
509//! use std::io::{Read, Write};
510//!
511//! let mut s = mockito::Server::new();
512//! let mock = s.mock("GET", "/hello").create();
513//!
514//! {
515//!     // Place a request
516//!     let mut stream = TcpStream::connect(s.host_with_port()).unwrap();
517//!     stream.write_all("GET /hello HTTP/1.1\r\n\r\n".as_bytes()).unwrap();
518//!     let mut response = String::new();
519//!     stream.read_to_string(&mut response).unwrap();
520//!     stream.flush().unwrap();
521//! }
522//!
523//! mock.assert();
524//! ```
525//!
526//! When several mocks can match a request, Mockito applies the first one that still expects requests.
527//! You can use this behaviour to provide **different responses for subsequent requests to the same endpoint**.
528//!
529//! ## Example
530//!
531//! ```
532//! use std::net::TcpStream;
533//! use std::io::{Read, Write};
534//!
535//! let mut s = mockito::Server::new();
536//! let english_hello_mock = s.mock("GET", "/hello").with_body("good bye").create();
537//! let french_hello_mock = s.mock("GET", "/hello").with_body("au revoir").create();
538//!
539//! {
540//!     // Place a request to GET /hello
541//!     let mut stream = TcpStream::connect(s.host_with_port()).unwrap();
542//!     stream.write_all("GET /hello HTTP/1.1\r\n\r\n".as_bytes()).unwrap();
543//!     let mut response = String::new();
544//!     stream.read_to_string(&mut response).unwrap();
545//!     stream.flush().unwrap();
546//! }
547//!
548//! english_hello_mock.assert();
549//!
550//! {
551//!     // Place another request to GET /hello
552//!     let mut stream = TcpStream::connect(s.host_with_port()).unwrap();
553//!     stream.write_all("GET /hello HTTP/1.1\r\n\r\n".as_bytes()).unwrap();
554//!     let mut response = String::new();
555//!     stream.read_to_string(&mut response).unwrap();
556//!     stream.flush().unwrap();
557//! }
558//!
559//! french_hello_mock.assert();
560//! ```
561//!
562//! If you're expecting more than 1 request, you can use the [`Mock::expect`] method to specify the exact amount of requests:
563//!
564//! ## Example
565//!
566//! ```no_run
567//! use std::net::TcpStream;
568//! use std::io::{Read, Write};
569//!
570//! let mut s = mockito::Server::new();
571//!
572//! let mock = s.mock("GET", "/hello").expect(3).create();
573//!
574//! for _ in 0..3 {
575//!     // Place a request
576//!     let mut stream = TcpStream::connect(s.host_with_port()).unwrap();
577//!     stream.write_all("GET /hello HTTP/1.1\r\n\r\n".as_bytes()).unwrap();
578//!     let mut response = String::new();
579//!     stream.read_to_string(&mut response).unwrap();
580//!     stream.flush().unwrap();
581//! }
582//!
583//! mock.assert();
584//! ```
585//!
586//! You can also work with ranges, by using the [`Mock::expect_at_least`] and [`Mock::expect_at_most`] methods:
587//!
588//! ## Example
589//!
590//! ```no_run
591//! use std::net::TcpStream;
592//! use std::io::{Read, Write};
593//!
594//! let mut s = mockito::Server::new();
595//!
596//! let mock = s.mock("GET", "/hello").expect_at_least(2).expect_at_most(4).create();
597//!
598//! for _ in 0..3 {
599//!     // Place a request
600//!     let mut stream = TcpStream::connect(s.host_with_port()).unwrap();
601//!     stream.write_all("GET /hello HTTP/1.1\r\n\r\n".as_bytes()).unwrap();
602//!     let mut response = String::new();
603//!     stream.read_to_string(&mut response).unwrap();
604//!     stream.flush().unwrap();
605//! }
606//!
607//! mock.assert();
608//! ```
609//!
610//! The errors produced by the [`Mock::assert`] method contain information about the tested mock, but also about the
611//! **last unmatched request**, which can be very useful to track down an error in your implementation or
612//! a missing or incomplete mock. A colored diff is also displayed:
613//!
614//! ![colored-diff.png](https://raw.githubusercontent.com/lipanski/mockito/master/docs/colored-diff.png)
615//!
616//! Color output is enabled by default, but can be toggled with the `color` feature flag.
617//!
618//! Here's an example of how a [`Mock::assert`] error looks like:
619//!
620//! ```text
621//! > Expected 1 request(s) to:
622//!
623//! POST /users?number=one
624//! bob
625//!
626//! ...but received 0
627//!
628//! > The last unmatched request was:
629//!
630//! POST /users?number=two
631//! content-length: 5
632//! alice
633//!
634//! > Difference:
635//!
636//! # A colored diff
637//!
638//! ```
639//!
640//! You can also use the [`Mock::matched`] method to return a boolean for whether the mock was called the
641//! correct number of times without panicking
642//!
643//! ## Example
644//!
645//! ```
646//! use std::net::TcpStream;
647//! use std::io::{Read, Write};
648//!
649//! let mut s = mockito::Server::new();
650//!
651//! let mock = s.mock("GET", "/").create();
652//!
653//! {
654//!     let mut stream = TcpStream::connect(s.host_with_port()).unwrap();
655//!     stream.write_all("GET / HTTP/1.1\r\n\r\n".as_bytes()).unwrap();
656//!     let mut response = String::new();
657//!     stream.read_to_string(&mut response).unwrap();
658//!     stream.flush().unwrap();
659//! }
660//!
661//! assert!(mock.matched());
662//!
663//! {
664//!     let mut stream = TcpStream::connect(s.host_with_port()).unwrap();
665//!     stream.write_all("GET / HTTP/1.1\r\n\r\n".as_bytes()).unwrap();
666//!     let mut response = String::new();
667//!     stream.read_to_string(&mut response).unwrap();
668//!     stream.flush().unwrap();
669//! }
670//! assert!(!mock.matched());
671//! ```
672//!
673//! # Non-matching calls
674//!
675//! Any calls to the Mockito server that are not matched will return *501 Not Implemented*.
676//!
677//! Note that **mocks are matched in reverse order** - the most recent one wins.
678//!
679//! # Cleaning up
680//!
681//! As mentioned earlier, mocks are cleaned up whenever the server goes out of scope. If you
682//! need to remove them earlier, you can call [`Server::reset`] to remove all mocks registered
683//! so far:
684//!
685//! ```
686//! let mut s = mockito::Server::new();
687//!
688//! s.mock("GET", "/1").create();
689//! s.mock("GET", "/2").create();
690//! s.mock("GET", "/3").create();
691//!
692//! s.reset();
693//!
694//! // Nothing is mocked at this point
695//! ```
696//!
697//! ...or you can call [`Mock::remove`] to remove a single mock:
698//!
699//! ```
700//! let mut s = mockito::Server::new();
701//!
702//! let m1 = s.mock("GET", "/1").create();
703//! let m2 = s.mock("GET", "/2").create();
704//!
705//! m1.remove();
706//!
707//! // Only m2 is available at this point
708//! ```
709//!
710//! # Debug
711//!
712//! Mockito uses the `env_logger` crate under the hood to provide useful debugging information.
713//!
714//! If you'd like to activate the debug output, introduce the [env_logger](https://crates.rs/crates/env_logger) crate
715//! to your project and initialize it before each test that needs debugging:
716//!
717//! ```
718//! #[test]
719//! fn example_test() {
720//!     let _ = env_logger::try_init();
721//!     // ...
722//! }
723//! ```
724//!
725//! Run your tests with:
726//!
727//! ```sh
728//! RUST_LOG=mockito=debug cargo test
729//! ```
730//!
731pub use error::{Error, ErrorKind};
732#[allow(deprecated)]
733pub use matcher::Matcher;
734pub use mock::{IntoHeaderName, Mock};
735pub use request::Request;
736pub use server::{Server, ServerOpts};
737pub use server_pool::ServerGuard;
738
739mod diff;
740mod error;
741mod matcher;
742mod mock;
743mod request;
744mod response;
745mod server;
746mod server_pool;