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//! 
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//! 
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;