wiremock 0.6.0

HTTP mocking to test Rust applications.
Documentation
use futures::FutureExt;
use serde::Serialize;
use serde_json::json;
use std::net::TcpStream;
use std::time::Duration;
use surf::StatusCode;
use wiremock::matchers::{body_json, body_partial_json, method, path, PathExactMatcher};
use wiremock::{Mock, MockServer, ResponseTemplate};

#[async_std::test]
async fn new_starts_the_server() {
    // Act
    let mock_server = MockServer::start().await;

    // Assert
    assert!(TcpStream::connect(mock_server.address()).is_ok())
}

#[async_std::test]
async fn returns_404_if_nothing_matches() {
    // Arrange - no mocks mounted
    let mock_server = MockServer::start().await;

    // Act
    let status = surf::get(&mock_server.uri()).await.unwrap().status();

    // Assert
    assert_eq!(status, 404);
}

#[async_std::test]
#[should_panic]
async fn panics_if_the_expectation_is_not_satisfied() {
    // Arrange
    let mock_server = MockServer::start().await;
    let response = ResponseTemplate::new(200);
    Mock::given(method("GET"))
        .respond_with(response)
        .expect(1..)
        .named("panics_if_the_expectation_is_not_satisfied expectation failed")
        .mount(&mock_server)
        .await;

    // Act - we never call the mock
}

#[async_std::test]
#[should_panic(expected = "Verifications failed:
- Mock #0.
\tExpected range of matching incoming requests: 1 <= x
\tNumber of matched incoming requests: 0

The server did not receive any request.")]
async fn no_received_request_line_is_printed_in_the_panic_message_if_expectations_are_not_verified()
{
    // Arrange
    let mock_server = MockServer::start().await;
    let response = ResponseTemplate::new(200);
    Mock::given(method("GET"))
        .respond_with(response)
        .expect(1..)
        .mount(&mock_server)
        .await;

    // Act - we never call the mock
}

#[async_std::test]
#[should_panic(expected = "Verifications failed:
- Mock #0.
\tExpected range of matching incoming requests: 1 <= x
\tNumber of matched incoming requests: 0

Received requests:
- Request #1
\tGET http://localhost/")]
async fn received_request_are_printed_as_panic_message_if_expectations_are_not_verified() {
    // Arrange
    let mock_server = MockServer::start().await;
    let response = ResponseTemplate::new(200);
    Mock::given(method("POST"))
        .respond_with(response)
        .expect(1..)
        .mount(&mock_server)
        .await;

    // Act - we sent a request that does not match (GET)
    surf::get(&mock_server.uri()).await.unwrap();

    // Assert - verified on drop
}

#[async_std::test]
#[should_panic]
async fn panic_during_expectation_does_not_crash() {
    // Arrange
    let mock_server = MockServer::start().await;
    let response = ResponseTemplate::new(200);
    Mock::given(method("GET"))
        .respond_with(response)
        .expect(1..)
        .named("panic_during_expectation_does_not_crash expectation failed")
        .mount(&mock_server)
        .await;

    // Act - start a panic
    panic!("forced panic")
}

#[async_std::test]
async fn simple_route_mock() {
    // Arrange
    let mock_server = MockServer::start().await;
    let response = ResponseTemplate::new(200).set_body_bytes("world");
    let mock = Mock::given(method("GET"))
        .and(PathExactMatcher::new("hello"))
        .respond_with(response);
    mock_server.register(mock).await;

    // Act
    let mut response = surf::get(format!("{}/hello", &mock_server.uri()))
        .await
        .unwrap();

    // Assert
    assert_eq!(response.status(), 200);
    assert_eq!(response.body_string().await.unwrap(), "world");
}

#[async_std::test]
async fn two_route_mocks() {
    // Arrange
    let mock_server = MockServer::start().await;

    // First
    let response = ResponseTemplate::new(200).set_body_bytes("aaa");
    Mock::given(method("GET"))
        .and(PathExactMatcher::new("first"))
        .respond_with(response)
        .named("/first")
        .mount(&mock_server)
        .await;

    // Second
    let response = ResponseTemplate::new(200).set_body_bytes("bbb");
    Mock::given(method("GET"))
        .and(PathExactMatcher::new("second"))
        .respond_with(response)
        .named("/second")
        .mount(&mock_server)
        .await;

    // Act
    let mut first_response = surf::get(format!("{}/first", &mock_server.uri()))
        .await
        .unwrap();
    let mut second_response = surf::get(format!("{}/second", &mock_server.uri()))
        .await
        .unwrap();

    // Assert
    assert_eq!(first_response.status(), 200);
    assert_eq!(second_response.status(), 200);

    assert_eq!(first_response.body_string().await.unwrap(), "aaa");
    assert_eq!(second_response.body_string().await.unwrap(), "bbb");
}

#[async_std::test]
async fn body_json_matches_independent_of_key_ordering() {
    #[derive(Serialize)]
    struct X {
        b: u8,
        a: u8,
    }

    // Arrange
    let expected_body = json!({ "a": 1, "b": 2 });
    let body = serde_json::to_string(&X { a: 1, b: 2 }).unwrap();

    let mock_server = MockServer::start().await;
    let response = ResponseTemplate::new(200);
    let mock = Mock::given(method("POST"))
        .and(body_json(expected_body))
        .respond_with(response);
    mock_server.register(mock).await;

    // Act
    let response = surf::post(mock_server.uri()).body(body).await.unwrap();

    // Assert
    assert_eq!(response.status(), StatusCode::Ok);
}

#[async_std::test]
async fn body_json_partial_matches_a_part_of_response_json() {
    // Arrange
    let expected_body = json!({ "a": 1, "c": { "e": 2 } });
    let body = json!({ "a": 1, "b": 2, "c": { "d": 1, "e": 2 } });

    let mock_server = MockServer::start().await;
    let response = ResponseTemplate::new(200);
    let mock = Mock::given(method("POST"))
        .and(body_partial_json(expected_body))
        .respond_with(response);
    mock_server.register(mock).await;

    // Act
    let response = surf::post(mock_server.uri()).body(body).await.unwrap();

    // Assert
    assert_eq!(response.status(), StatusCode::Ok);
}

#[should_panic(expected = "\
Wiremock can't match the path `abcd?` because it contains a `?`. You must use `wiremock::matchers::query_param` to match on query parameters (the part of the path after the `?`).")]
#[async_std::test]
async fn query_parameter_is_not_accepted_in_path() {
    Mock::given(method("GET")).and(path("abcd?"));
}

#[should_panic(expected = "\
Wiremock can't match the path `https://domain.com/abcd` because it contains the host `domain.com`. You don't have to specify the host - wiremock knows it. Try replacing your path with `path(\"/abcd\")`")]
#[async_std::test]
async fn host_is_not_accepted_in_path() {
    Mock::given(method("GET")).and(path("https://domain.com/abcd"));
}

#[async_std::test]
async fn use_mock_guard_to_verify_requests_from_mock() {
    // Arrange
    let mock_server = MockServer::start().await;

    let first = mock_server
        .register_as_scoped(
            Mock::given(method("POST"))
                .and(PathExactMatcher::new("first"))
                .respond_with(ResponseTemplate::new(200)),
        )
        .await;

    let second = mock_server
        .register_as_scoped(
            Mock::given(method("POST"))
                .and(PathExactMatcher::new("second"))
                .respond_with(ResponseTemplate::new(200)),
        )
        .await;

    // Act
    let uri = mock_server.uri();
    let response = surf::post(format!("{uri}/first"))
        .body(json!({ "attempt": 1}))
        .await
        .unwrap();
    assert_eq!(response.status(), StatusCode::Ok);

    let response = surf::post(format!("{uri}/first"))
        .body(json!({ "attempt": 2}))
        .await
        .unwrap();
    assert_eq!(response.status(), StatusCode::Ok);

    let response = surf::post(format!("{uri}/second"))
        .body(json!({ "attempt": 99}))
        .await
        .unwrap();
    assert_eq!(response.status(), StatusCode::Ok);

    // Assert
    let all_requests_to_first = first.received_requests().await;
    assert_eq!(all_requests_to_first.len(), 2);

    let value: serde_json::Value = second.received_requests().await[0].body_json().unwrap();

    assert_eq!(value, json!({"attempt": 99}));
}

#[async_std::test]
async fn use_mock_guard_to_await_satisfaction_readiness() {
    // Arrange
    let mock_server = MockServer::start().await;

    let satisfy = mock_server
        .register_as_scoped(
            Mock::given(method("POST"))
                .and(PathExactMatcher::new("satisfy"))
                .respond_with(ResponseTemplate::new(200))
                .expect(1),
        )
        .await;

    let eventually_satisfy = mock_server
        .register_as_scoped(
            Mock::given(method("POST"))
                .and(PathExactMatcher::new("eventually_satisfy"))
                .respond_with(ResponseTemplate::new(200))
                .expect(1),
        )
        .await;

    // Act one
    let uri = mock_server.uri();
    let response = surf::post(format!("{uri}/satisfy")).await.unwrap();
    assert_eq!(response.status(), StatusCode::Ok);

    // Assert
    satisfy
        .wait_until_satisfied()
        .now_or_never()
        .expect("should be satisfied immediately");

    eventually_satisfy
        .wait_until_satisfied()
        .now_or_never()
        .ok_or(())
        .expect_err("should not be satisfied yet");

    // Act two
    async_std::task::spawn(async move {
        async_std::task::sleep(Duration::from_millis(100)).await;
        let response = surf::post(format!("{uri}/eventually_satisfy"))
            .await
            .unwrap();
        assert_eq!(response.status(), StatusCode::Ok);
    });

    // Assert
    eventually_satisfy
        .wait_until_satisfied()
        .now_or_never()
        .ok_or(())
        .expect_err("should not be satisfied yet");

    async_std::io::timeout(
        Duration::from_millis(1000),
        eventually_satisfy.wait_until_satisfied().map(Ok),
    )
    .await
    .expect("should be satisfied");
}

#[async_std::test]
async fn debug_prints_mock_server_variants() {
    let pooled_mock_server = MockServer::start().await;
    let pooled_debug_str = format!("{:?}", pooled_mock_server);

    assert!(pooled_debug_str.starts_with("MockServer(Pooled(Object {"));
    assert!(pooled_debug_str
        .find(
            format!(
                "BareMockServer {{ address: {} }}",
                pooled_mock_server.address()
            )
            .as_str()
        )
        .is_some());

    let bare_mock_server = MockServer::builder().start().await;
    assert_eq!(
        format!(
            "MockServer(Bare(BareMockServer {{ address: {} }}))",
            bare_mock_server.address()
        ),
        format!("{:?}", bare_mock_server)
    );
}