mockito

repository·master·Indexed 21 days ago

https://github.com/lipanski/mockito

A Rust library for generating and delivering HTTP mocks, designed for integration testing and offline development. It provides sync and async interfaces to simulate complex HTTP interactions by running a local pool of HTTP servers, allowing developers to define request matching criteria (path, query, headers, body) and specify expected response status codes, headers, and bodies.

Tokens
8K
Snippets
30
Records
34
Agent score
70%

What's inside mockito

  1. Using Mockito in async tests

    master

    Mockito provides an asynchronous interface for use in async runtimes like tokio. When writing async tests, ensure you use the _async variants of the methods:

    • Server::new_async().await to create the server.
    • .create_async().await to create the mock.
    • Mock::assert_async().await to verify the mock was called.

    Example:

    #[tokio::test]
    async fn test_simple_route_mock_async() {
        let mut server = Server::new_async().await;
        let m1 = server.mock("GET", "/a").with_body("aaa").create_async().await;
        let m2 = server.mock("GET", "/b").with_body("bbb").create_async().await;
    
        let (m1, m2) = futures::join!(m1, m2);
    
        // You can use `Mock::assert_async` to verify that your mock was called
        // m1.assert_async().await;
        // m2.assert_async().await;
    }
  2. Basic usage of Mockito Server and Mocks

    master

    Mockito manages a pool of HTTP servers. To mock an endpoint, request a new server from the pool, use its URL to configure your client, and define a mock using the .mock() method.

    Key steps:

    1. Create a server: mockito::Server::new().
    2. Get the URL: server.url().
    3. Define the mock: .mock(METHOD, PATH) followed by modifiers like .with_status(), .with_header(), and .with_body().
    4. Finalize the mock: .create().
    5. Verify usage: mock.assert().

    If mock.assert() fails, Mockito prints a colored diff of the last unmatched request to help you debug.

    #[test]
    fn test_something() {
        // Request a new server from the pool
        let mut server = mockito::Server::new();
    
        // Use one of these addresses to configure your client
        let host = server.host_with_port();
        let url = server.url();
    
        // Create a mock
        let mock = server.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".
    
        // You can use `Mock::assert` to verify that your mock was called
        mock.assert();
    }
  3. Install Mockito via Cargo

    master

    To use Mockito in your Rust project, add it to your Cargo.toml dependencies.

    Note: The minimum supported Rust toolchain is 1.85.0 (as of current version).

    # Add to Cargo.toml
    [dependencies]
    mockito = "1.7.2"
  4. Match requests using Matchers

    master

    Mockito allows you to match requests based on path, query, headers, or body using the Matcher type.

    Path Matching

    • Exact: server.mock("GET", "/hello") matches only /hello.
    • Regex: Use Matcher::Regex for partial path matching.
    • Any: Use Matcher::Any to match any path.

    Query Matching

    Use Mock::match_query with matchers like Matcher::UrlEncoded or Matcher::Regex. Note that UrlEncoded arguments should be in plain (unencoded) format.

    Header Matching

    • Exact: match_header("name", "value").
    • Regex: match_header("name", Matcher::Regex("...")).
    • Presence only: match_header("name", Matcher::Any) matches if the header exists regardless of value.
    • Absence: match_header("name", Matcher::Missing) matches only if the header is absent.

    Body Matching

    • Exact: match_body("string").
    • Regex: match_body(Matcher::Regex("...")).
    • JSON: match_body(Matcher::Json(json!({...}))) or match_body(Matcher::JsonString("...")).

    Logical Combinators

    • Matcher::AnyOf(vec![...]): Matches if at least one matcher matches.
    • Matcher::AllOf(vec![...]): Matches if all matchers match.
    // Example: Matching by Regex Path
    s.mock("GET", mockito::Matcher::Regex(r"^/hello/(1|2)$".to_string())).create();
    
    // Example: Matching by Query
    s.mock("GET", "/test")
      .match_query(mockito::Matcher::UrlEncoded("greeting".into(), "good day".into()))
      .create();
    
    // Example: Matching by JSON Body
    s.mock("POST", "/").match_body(mockito::Matcher::JsonString(r#"{"hello": "world"}"# .to_string())).create();
  5. How Mockito servers and mocks work together

    master

    A Server manages a pool of HTTP servers. A Mock is defined on a specific Server and is only available throughout the lifetime of that server. Once the Server goes out of scope, all mocks defined on it are removed. You can remove individual mocks earlier by calling Mock::remove or clear all mocks on a server using Server::reset.

    let address;
    
    {
        let mut s = mockito::Server::new();
        address = s.host_with_port();
    
        s.mock("GET", "/").with_body("hi").create();
    
        // Requests to `address` will be responded with "hi" til here
    }
    
    // Requests to `address` will fail as of this point
  6. How ServerGuard manages server lifecycle

    master

    A ServerGuard is a smart pointer that provides access to a pooled Server. It implements Deref and DerefMut, allowing it to be used as if it were a Server instance.

    Crucially, ServerGuard handles automatic resource recycling: when the guard is dropped, the underlying Server is automatically reset and returned to the internal SERVER_POOL for reuse. This ensures that the pool maintains its capacity and that servers are cleaned up between tests.

    // ServerGuard can be used directly as a Server due to Deref/DerefMut
    // When it goes out of scope, the server is automatically recycled.
    {
        let mut server_guard = pool.get_async().await?;
        server_guard.some_server_method();
    }
  7. Getting Started with Mockito

    master

    Mockito is a Rust library for generating and delivering HTTP mocks. You can use it for integration testing or offline work by running a local pool of HTTP servers. To use it, add mockito to your Cargo.toml and follow the pattern of creating a Server, obtaining its URL, and defining Mock objects.

    #[cfg(test)]
    mod tests {
      #[test]
      fn test_something() {
        // Request a new server from the pool
        let mut server = mockito::Server::new();
    
        // Use one of these addresses to configure your client
        let host = server.host_with_port();
        let url = server.url();
    
        // Create a mock
        let mock = server.mock("GET", "/hello")
          .with_status(201)
          .with_header("content-type", "text/plain")
          .with_header("x-api-key", "1234")
          .with_body("world")
          .create();
    
        // You can use `Mock::assert` to verify that your mock was called
        // mock.assert();
      }
    }
  8. Use async interfaces for async tests

    master

    Mockito provides both sync and async interfaces. If you are writing asynchronous tests (e.g., using #[tokio::test]), you must use the *_async variants of the methods to avoid blocking the runtime.

    Required async methods:

    • Server::new_async
    • Server::new_with_opts_async
    • Mock::create_async
    • Mock::assert_async
    • Mock::matched_async
    • Mock::remove_async
    #[cfg(test)]
    mod tests {
    # use mockito::Server;
      #[tokio::test]
      async fn test_something() {
        let mut server = Server::new_async().await;
        let m1 = server.mock("GET", "/a").with_body("aaa").create_async().await;
        let m2 = server.mock("GET", "/b").with_body("bbb").create_async().await;
    
        let (m1, m2) = futures::join!(m1, m2);
    
        // You can use `Mock::assert_async` to verify that your mock was called
        // m1.assert_async().await;
        // m2.assert_async().await;
      }
    }
  9. Initialize a mock server

    master

    Mockito uses a server pool to manage running servers. The pool has a capacity of 50. Most of the time, you should use Server::new() to fetch the next available instance from the pool.

    If you need to bypass the pool or configure specific settings (like a custom host, port, or enabling auto-assertions), use Server::new_with_opts().

    // Fetch from the server pool
    let mut server = mockito::Server::new();
    
    // Or bypass the pool with custom options
    let opts = mockito::ServerOpts { port: 1234, ..Default::default() };
    let server_with_port = mockito::Server::new_with_opts(opts);
  10. Mocking multiple hosts simultaneously

    master

    You can simulate interactions with multiple different services by starting multiple mockito::Server instances. Each server will have its own unique URL.

    Example:

    #[test]
    fn test_something() {
        let mut twitter = mockito::Server::new();
        let mut github = mockito::Server::new();
    
        // These mocks will be available at `twitter.url()`
        let twitter_mock = twitter.mock("GET", "/api").create();
    
        // These mocks will be available at `github.url()`
        let github_mock = github.mock("GET", "/api").create();
    }
  11. Start a standalone server with custom options

    master

    If you need a server running on a specific, dedicated port (e.g., for a long-running process or manual testing), use Server::new_with_opts with a ServerOpts configuration.

    ServerOpts allows you to specify:

    • host: The IP address to bind to (e.g., "0.0.0.0").
    • port: The specific port number.

    Example:

    fn main() {
        let opts = mockito::ServerOpts {
            host: "0.0.0.0",
            port: 1234,
            ..Default::default()
        };
        let mut server = mockito::Server::new_with_opts(opts);
    
        let _m = server.mock("GET", "/").with_body("hello world").create();
    
        loop {}
    }
  12. Use Matchers to differentiate requests

    master

    When multiple requests are sent to the same endpoint, use matchers to ensure the correct mock responds to the correct request based on headers or body content.

    Supported matchers include:

    • match_header(name, value)
    • match_body(matcher)

    Common mockito::Matcher variants:

    • PartialJsonString(String): Matches if the body contains a specific JSON structure.
    • Regex(String): Matches the body against a regular expression.

    Example of using matchers for different content types on the same path:

    #[test]
    fn test_something() {
        let mut server = mockito::Server::new();
    
        server.mock("GET", "/greetings")
          .match_header("content-type", "application/json")
          .match_body(mockito::Matcher::PartialJsonString(
              "{\"greeting\": \"hello\"}".to_string(),
          ))
          .with_body("hello json")
          .create();
    
        server.mock("GET", "/greetings")
          .match_header("content-type", "application/text")
          .match_body(mockito::Matcher::Regex("greeting=hello".to_string()))
          .with_body("hello text")
          .create();
    }