[−][src]Crate mockito
Mockito is a library for creating HTTP mocks to be used in integration tests or for offline work. It runs an HTTP server on a local port which delivers, creates and remove the mocks.
The server is run on a separate thread within the same process and will be removed at the end of the run.
Getting Started
Use mockito::server_url()
or mockito::server_address()
as the base URL for any mocked
client in your tests. One way to do this is by using compiler flags:
Example
#[cfg(test)] use mockito; fn main() { #[cfg(not(test))] let url = "https://api.twitter.com"; #[cfg(test)] let url = &mockito::server_url(); // Use url as the base URL for your client }
Then start mocking:
Example
#[cfg(test)] mod tests { use mockito::mock; #[test] fn test_something() { let _m = mock("GET", "/hello") .with_status(201) .with_header("content-type", "text/plain") .with_header("x-api-key", "1234") .with_body("world") .create(); // Any calls to GET /hello beyond this line will respond with 201, the // `content-type: text/plain` header and the body "world". } }
Lifetime
Just like any Rust object, a mock is available only through its lifetime. You'll want to assign the mocks to variables in order to extend and control their lifetime.
Avoid using the underscore matcher when creating your mocks, as in let _ = mock("GET", "/")
.
This will end your mock's lifetime immediately. You can still use the underscore to prefix your variable
names in an assignment, but don't limit it to just this one character.
Example
use mockito::mock; let _m1 = mock("GET", "/long").with_body("hello").create(); { let _m2 = mock("GET", "/short").with_body("hi").create(); // Requests to GET /short will be mocked til here } // Requests to GET /long will be mocked til here
Limitations
Creating mocks from threads is currently not possible. Please use the main (test) thread for that. See the note on threads at the end for more details.
Asserts
You can use the Mock::assert
method to assert that a mock was called. In other words, the
Mock#assert
method can validate that your code performed the expected HTTP requests.
By default, the method expects that only one request to your mock was triggered.
Example
use std::net::TcpStream; use std::io::{Read, Write}; use mockito::{mock, server_address}; let mock = mock("GET", "/hello").create(); { // Place a request let mut stream = TcpStream::connect(server_address()).unwrap(); stream.write_all("GET /hello HTTP/1.1\r\n\r\n".as_bytes()).unwrap(); let mut response = String::new(); stream.read_to_string(&mut response).unwrap(); stream.flush().unwrap(); } mock.assert();
When several mocks can match a request, Mockito applies the first one that still expects requests. You can use this behaviour to provide different responses for subsequent requests to the same endpoint.
Example
use std::net::TcpStream; use std::io::{Read, Write}; use mockito::{mock, server_address}; let english_hello_mock = mock("GET", "/hello").with_body("good bye").create(); let french_hello_mock = mock("GET", "/hello").with_body("au revoir").create(); { // Place a request to GET /hello let mut stream = TcpStream::connect(server_address()).unwrap(); stream.write_all("GET /hello HTTP/1.1\r\n\r\n".as_bytes()).unwrap(); let mut response = String::new(); stream.read_to_string(&mut response).unwrap(); stream.flush().unwrap(); } english_hello_mock.assert(); { // Place another request to GET /hello let mut stream = TcpStream::connect(server_address()).unwrap(); stream.write_all("GET /hello HTTP/1.1\r\n\r\n".as_bytes()).unwrap(); let mut response = String::new(); stream.read_to_string(&mut response).unwrap(); stream.flush().unwrap(); } french_hello_mock.assert();
If you're expecting more than 1 request, you can use the Mock::expect
method to specify the exact amount of requests:
Example
use std::net::TcpStream; use std::io::{Read, Write}; use mockito::{mock, server_address}; let mock = mockito::mock("GET", "/hello").expect(3).create(); for _ in 0..3 { // Place a request let mut stream = TcpStream::connect(server_address()).unwrap(); stream.write_all("GET /hello HTTP/1.1\r\n\r\n".as_bytes()).unwrap(); let mut response = String::new(); stream.read_to_string(&mut response).unwrap(); stream.flush().unwrap(); } mock.assert();
You can also work with ranges, by using the Mock::expect_at_least
and Mock::expect_at_most
methods:
Example
use std::net::TcpStream; use std::io::{Read, Write}; use mockito::{mock, server_address}; let mock = mockito::mock("GET", "/hello").expect_at_least(2).expect_at_most(4).create(); for _ in 0..3 { // Place a request let mut stream = TcpStream::connect(server_address()).unwrap(); stream.write_all("GET /hello HTTP/1.1\r\n\r\n".as_bytes()).unwrap(); let mut response = String::new(); stream.read_to_string(&mut response).unwrap(); stream.flush().unwrap(); } mock.assert();
The errors produced by the assert
method contain information about the tested mock, but also about the
last unmatched request, which can be very useful to track down an error in your implementation or
a missing or incomplete mock. A colored diff is also displayed.
Color output is enabled by default, but can be toggled with the color
feature flag.
Here's an example of how a Mock#assert
error looks like:
> Expected 1 request(s) to:
POST /users?number=one
bob
...but received 0
> The last unmatched request was:
POST /users?number=two
content-length: 5
alice
> Difference:
# A colored diff
You can also use the matched
method to return a boolean for whether the mock was called the
correct number of times without panicking
Example
use std::net::TcpStream; use std::io::{Read, Write}; use mockito::{mock, server_address}; let mock = mock("GET", "/").create(); { let mut stream = TcpStream::connect(server_address()).unwrap(); stream.write_all("GET / HTTP/1.1\r\n\r\n".as_bytes()).unwrap(); let mut response = String::new(); stream.read_to_string(&mut response).unwrap(); stream.flush().unwrap(); } assert!(mock.matched()); { let mut stream = TcpStream::connect(server_address()).unwrap(); stream.write_all("GET / HTTP/1.1\r\n\r\n".as_bytes()).unwrap(); let mut response = String::new(); stream.read_to_string(&mut response).unwrap(); stream.flush().unwrap(); } assert!(!mock.matched());
Matchers
Mockito can match your request by method, path, query, headers or body.
Various matchers are provided by the Matcher
type: exact, partial (regular expressions), any or missing.
Matching by path
By default, the request path is compared by its exact value:
Example
use mockito::mock; // Matched only calls to GET /hello let _m = mock("GET", "/hello").create();
You can also match the path partially, by using a regular expression:
Example
use mockito::{mock, Matcher}; // Will match calls to GET /hello/1 and GET /hello/2 let _m = mock("GET", Matcher::Regex(r"^/hello/(1|2)$".to_string())).create();
Or you can catch all requests, by using the Matcher::Any
variant:
Example
use mockito::{mock, Matcher}; // Will match any GET request let _m = mock("GET", Matcher::Any).create();
Matching by query
You can match the query part by using the Mock#match_query
function together with the various matchers,
most notably Matcher::UrlEncoded
:
Example
use mockito::{mock, Matcher}; // This will match requests containing the URL-encoded // query parameter `greeting=good%20day` let _m1 = mock("GET", "/test") .match_query(Matcher::UrlEncoded("greeting".into(), "good day".into())) .create(); // This will match requests containing the URL-encoded // query parameters `hello=world` and `greeting=good%20day` let _m2 = mock("GET", "/test") .match_query(Matcher::AllOf(vec![ Matcher::UrlEncoded("hello".into(), "world".into()), Matcher::UrlEncoded("greeting".into(), "good day".into()) ])) .create(); // You can achieve similar results with the regex matcher let _m3 = mock("GET", "/test") .match_query(Matcher::Regex("hello=world".into())) .create();
Note that the key/value arguments for Matcher::UrlEncoded
should be left in plain (unencoded) format.
You can also specify the query as part of the path argument in a mock
call, in which case an exact
match will be performed:
Example
use mockito::mock; // This will perform a full match against the query part let _m = mock("GET", "/test?hello=world").create();
Matching by header
By default, headers are compared by their exact value. The header name letter case is ignored though.
Example
use mockito::mock; let _m1 = mock("GET", "/hello") .match_header("content-type", "application/json") .with_body(r#"{"hello": "world"}"#) .create(); let _m2 = mock("GET", "/hello") .match_header("content-type", "text/plain") .with_body("world") .create(); // JSON requests to GET /hello will respond with JSON, while plain requests // will respond with text.
You can also match a header value with a regular expressions, by using the Matcher::Regex
matcher:
Example
use mockito::{mock, Matcher}; let _m = mock("GET", "/hello") .match_header("content-type", Matcher::Regex(r".*json.*".to_string())) .with_body(r#"{"hello": "world"}"#) .create();
Or you can match a header only by its field name, by setting the Mock::match_header
value to Matcher::Any
.
Example
use mockito::{mock, Matcher}; let _m = mock("GET", "/hello") .match_header("content-type", Matcher::Any) .with_body("something") .create(); // Requests containing any content-type header value will be mocked. // Requests not containing this header will return `501 Mock Not Found`.
You can mock requests that should be missing a particular header field, by setting the Mock::match_header
value to Matcher::Missing
.
Example
use mockito::{mock, Matcher}; let _m = mock("GET", "/hello") .match_header("authorization", Matcher::Missing) .with_body("no authorization header") .create(); // Requests without the authorization header will be matched. // Requests containing the authorization header will return `501 Mock Not Found`.
Matching by body
You can match a request by its body by using the Mock#match_body
method.
By default the request body is ignored, similar to passing the Matcher::Any
argument to the match_body
method.
You can match a body by an exact value:
Example
use mockito::mock; // Will match requests to POST / whenever the request body is "hello" let _m = mock("POST", "/").match_body("hello").create();
Or you can match the body by using a regular expression:
Example
use mockito::{mock, Matcher}; // Will match requests to POST / whenever the request body *contains* the word "hello" (e.g. "hello world") let _m = mock("POST", "/").match_body(Matcher::Regex("hello".to_string())).create();
Or you can match the body using a JSON object:
Example
#[macro_use] extern crate serde_json; use mockito::{mock, Matcher}; // Will match requests to POST / whenever the request body matches the json object let _m = mock("POST", "/").match_body(Matcher::Json(json!({"hello": "world"}))).create();
If serde_json::json!
is not exposed, you can use Matcher::JsonString
the same way,
but by passing a String
to the matcher:
use mockito::{mock, Matcher}; // Will match requests to POST / whenever the request body matches the json object let _m = mock("POST", "/") .match_body( Matcher::JsonString(r#"{"hello": "world"}"#.to_string()) ) .create();
The AnyOf
matcher
The Matcher::AnyOf
construct takes a vector of matchers as arguments and will be enabled
if at least one of the provided matchers matches the request.
Example
use mockito::{mock, Matcher}; // Will match requests to POST / whenever the request body is either `hello=world` or `{"hello":"world"}` let _m = mock("POST", "/") .match_body( Matcher::AnyOf(vec![ Matcher::Exact("hello=world".to_string()), Matcher::JsonString(r#"{"hello": "world"}"#.to_string()), ]) ) .create();
The AllOf
matcher
The Matcher::AllOf
construct takes a vector of matchers as arguments and will be enabled
if all of the provided matchers match the request.
Example
use mockito::{mock, Matcher}; // Will match requests to POST / whenever the request body contains both `hello` and `world` let _m = mock("POST", "/") .match_body( Matcher::AllOf(vec![ Matcher::Regex("hello".to_string()), Matcher::Regex("world".to_string()), ]) ) .create();
Non-matching calls
Any calls to the Mockito server that are not matched will return 501 Mock Not Found.
Note that mocks are matched in reverse order - the most recent one wins.
Cleaning up
As mentioned earlier, mocks are cleaned up at the end of their normal Rust lifetime. However,
you can always use the reset
method to clean up all the mocks registered so far.
Example
use mockito::{mock, reset}; let _m1 = mock("GET", "/1").create(); let _m2 = mock("GET", "/2").create(); let _m3 = mock("GET", "/3").create(); reset(); // Nothing is mocked at this point
Or you can use std::mem::drop
to remove a single mock without having to wait for its scope to end:
Example
use mockito::mock; use std::mem; let m = mock("GET", "/hello").create(); // Requests to GET /hello are mocked mem::drop(m); // Still in the scope of `m`, but requests to GET /hello aren't mocked any more
Debug
Mockito uses the env_logger
crate under the hood to provide useful debugging information.
If you'd like to activate the debug output, introduce the env_logger crate within your project and initialize it before each test that needs debugging:
#[test] fn example_test() { let _ = env_logger::try_init(); // ... }
Run your tests with:
RUST_LOG=mockito=debug cargo test
Threads
Mockito records all your mocks on the same server running in the background. For this reason, Mockito tests are run sequentially. This is handled internally via a thread-local mutex lock acquired whenever you create a mock. Tests that don't create mocks will still be run in parallel.
Structs
BinaryBody | Represents a binary object the body should be matched against |
Mock | Stores information about a mocked request. Should be initialized via |
Enums
Matcher | Allows matching the request path or headers in multiple ways: matching the exact value, matching any value (as long as it is present), matching by regular expression or checking that a particular header is missing. |
Constants
SERVER_ADDRESS | Deprecated Points to the address the mock server is running at.
Can be used with |
SERVER_URL | Deprecated Points to the URL the mock server is running at. |
Functions
mock | Initializes a mock for the provided |
reset | Removes all the mocks stored on the server. |
server_address | Address and port of the local server.
Can be used with |
server_url | A local |
start |