Code Monkey home page Code Monkey logo

httpmock's Introduction

httpmock

HTTP mocking library for Rust.

Build codecov crates.io Mentioned in Awesome Rust

Documentation · Crate · Report Bug · Request Feature · Changelog · Support this Project

Features

  • Simple, expressive, fluent API.
  • Many built-in helpers for easy request matching (Regex, JSON, serde, cookies, and more).
  • Parallel test execution.
  • Extensible request matching.
  • Fully asynchronous core with synchronous and asynchronous APIs.
  • Advanced verification and debugging support (including diff generation between actual and expected HTTP request values)
  • Fault and network delay simulation.
  • Support for Regex matching, JSON, serde, cookies, and more.
  • Standalone mode with an accompanying Docker image.
  • Support for mock configuration using YAML files.

Getting Started

Add httpmock to Cargo.toml:

[dev-dependencies]
httpmock = "0.7.0"

You can then use httpmock as follows:

use httpmock::prelude::*;

// Start a lightweight mock server.
let server = MockServer::start();

// Create a mock on the server.
let mock = server.mock(|when, then| {
    when.method(GET)
        .path("/translate")
        .query_param("word", "hello");
    then.status(200)
        .header("content-type", "text/html; charset=UTF-8")
        .body("Привет");
});

// Send an HTTP request to the mock server. This simulates your code.
let response = isahc::get(server.url("/translate?word=hello")).unwrap();

// Ensure the specified mock was called exactly one time (or fail with a
// detailed error description).
mock.assert();

// Ensure the mock server did respond as specified.
assert_eq!(response.status(), 200);

The above example will spin up a lightweight HTTP mock server and configure it to respond to all GET requests to path /translate with query parameter word=hello. The corresponding HTTP response will contain the text body Привет.

In case the request fails, httpmock would show you a detailed error description including a diff between the expected and the actual HTTP request:

colored-diff.png

Usage

See the reference docs for detailed API documentation.

Examples

You can find examples in the httpmock test directory. The reference docs also contain a lot of examples. There is an online tutorial as well.

Standalone Mock Server

You can use httpmock to run a standalone mock server that is executed in a separate process. There is a Docker image available at Dockerhub to get started quickly.

The standalone mode allows you to mock HTTP based APIs for many API clients, not only the ones inside your Rust tests, but also completely different programs running on remote hosts. This is especially useful if you want to use httpmock in system or end-to-end tests that require mocked services (such as REST APIs, data stores, authentication providers, etc.).

Please refer to the docs for more information

License

httpmock is free software: you can redistribute it and/or modify it under the terms of the MIT Public License.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MIT Public License for more details.

httpmock's People

Contributors

95th avatar alexliesenfeld avatar davidpdrsn avatar dax avatar incker avatar ivd-git avatar kianmeng avatar korrat avatar mmm-8192 avatar mythmon avatar n8henrie avatar nordzilla avatar sdbondi avatar seanpianka avatar tgasslander avatar the-wondersmith avatar theredfish avatar uebelandre avatar utkarshgupta137 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

httpmock's Issues

Signal 11 SIGSEGV in musl build

Issue

I try to run tests in a statically built environment. I need to use musl target for that. I got this error by only using this library. xxx is my binary.

Caused by:
  process didn't exit successfully: `/work/target/x86_64-unknown-linux-musl/debug/deps/xxx` (signal: 11, SIGSEGV: invalid memory reference)

Reproduce

Create any test which uses httpmock.

docker run --rm -it -e CARGO_HOME=/work/.cargo -v $(pwd):/work -w /work rust:1.57.0-alpine3.14 sh
apk add --no-cache gcc g++ musl-dev openssl-dev
RUST_LOG=libbindgen RUST_BACKTRACE=1 cargo test --target x86_64-unknown-linux-musl

Question about type correctness and the http crate

I just started using httpmock. One thing I immediately noticed is how it doesn't seem to be using the types I'd consider most appropriate.

The http crate defines commonly needed http related types which are used by a lot of well-known libraries in the ecosystem like tower, hyper and reqwest.

To my surprise, this crate doesn't. For example, it redefines the http Method enum instead of using the types from the http crate "everyone else" in the ecosystem uses. I think it would be better to use the common types.

Something else I noticed: When.header taking (Into<String>, Into<String>) instead of the (in my opinion) properly typed (Into<http::HeaderName>, Into<http::HeaderValue>). (Not every String is a valid http header, which is why in my opinion this type is not quite correct.)

Also, when.Body takes an Into<String>, but http can transport many things in its body, including binary data. This is accounted for in Then as it takes an impl AsRef<[u8]> but not in When, preventing (as far as I can tell) matching on non-string request bodies.

Are these intentional design choices? If not, would you consider pull requests adressing this?

Optionally disable parallelism

Is there any way to optionally disable parallelism between specific tests? In my project I have some complex tests that use global state and therefor can't be run in parallel. One solution I have found is to move them into their own integration test files, but I'm trying to keep like-tests in the same files.

RFC: simple statefull when-clauses

Scenario
In my test scenario, my client performs multiple similar requests and I need the mock server to respond differently. (In my scenario I want to emulate concurrent resource updates that my function under test should tolerate).

Problem
The requests are not distinguishable, i.e. I can't create two different when() clauses for the two (or more) requests.

Solution
The solution would be to make the mock server somewhat "statefull". The matches() method of When already captures custom function pointers to match the request body against. One possible solution that comes to mind would be to have the server optionally store a reference to a State trait that the test can implement. One could imagine a new clauses for when() and then() (let's name them check_state and update_state for the lack of a better name):

let first_request = server.mock(|when, then| {
    when.method(GET)
        .check_state(|state: impl State| {
// resource available
    };
    then.status(200););
});

let first_request = server.mock(|when, then| {
    when.method(DELETE);
    then.status(200)
           .update_state(|state: impl State| {
// update state
    };);
});

let second_request = server.mock(|when, then| {
    when.method(GET)
        .check_state(|state: impl State| {
// resource not available
    };
    then.status(404););
});

Build error caused by `futures-util=0.3.7`

I feel like futures-rs messed up by making that update a patch version change only but alas, It would be great to have this fixed.

httpmock % cargo test
   Compiling httpmock v0.5.1-alpha.0 (/Users/user/Code/httpmock)
error[E0433]: failed to resolve: could not find `core_reexport` in `futures_util`
   --> src/lib.rs:202:19
    |
202 | use futures_util::core_reexport::time::Duration;
    |                   ^^^^^^^^^^^^^ could not find `core_reexport` in `futures_util`

error[E0433]: failed to resolve: could not find `core_reexport` in `futures_util`
   --> src/lib.rs:202:19
    |
202 | use futures_util::core_reexport::time::Duration;
    |                   ^^^^^^^^^^^^^ could not find `core_reexport` in `futures_util`

error[E0412]: cannot find type `Duration` in this scope
    --> src/lib.rs:1407:26
     |
1407 |     pub fn delay<D: Into<Duration>>(self, duration: D) -> Self {
     |                          ^^^^^^^^ not found in this scope
     |
help: consider importing one of these items
     |
196  | use core::time::Duration;
     |
196  | use crate::data::Duration;
     |
196  | use std::time::Duration;
     |
196  | use tokio::time::Duration;
     |

error[E0412]: cannot find type `Duration` in this scope
    --> src/lib.rs:1407:26
     |
1407 |     pub fn delay<D: Into<Duration>>(self, duration: D) -> Self {
     |                          ^^^^^^^^ not found in this scope
     |
help: consider importing one of these items
     |
196  | use core::time::Duration;
     |
196  | use crate::data::Duration;
     |
196  | use std::time::Duration;
     |
196  | use tokio::time::Duration;
     |

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0412, E0433.
For more information about an error, try `rustc --explain E0412`.
error: could not compile `httpmock`.

To learn more, run the command again with --verbose.
warning: build failed, waiting for other jobs to finish...
error: aborting due to 2 previous errors

Some errors have detailed explanations: E0412, E0433.
For more information about an error, try `rustc --explain E0412`.
error: build failed

I also noticed a BUNCH of packages are out of date. Do you think it'd be possible to update them and as a check for any lingering uses of "deprecated" code?

FR: Support for tokio 1.0

I've done very little investigation here so I don't know the scope of this request but it'd be nice to update the version of tokio used here to help shrink my build graph. Hopefully this is as easy as changing the number 😅

Mocking multipart uploads

It seems the library fails on multipart/form-data uploads. It works fine with regular requests with json body.

Requests to real server upload files without any issues. I'm using ureq as HTTP client:

    fn request_with_form_data<T1: serde::ser::Serialize, T2: serde::de::DeserializeOwned>(
        &self,
        method: &str,
        params: T1,
        parameter_name: &str,
        file_path: PathBuf,
    ) -> Result<T2, ureq::Error> {
        let json_string = serde_json::to_string(&params).unwrap();
        let json_struct: Value = serde_json::from_str(&json_string).unwrap();

        let mut form = Multipart::new();
        for (key, val) in json_struct.as_object().unwrap().iter() {
            if key != parameter_name {
                let val = match val {
                    &Value::String(ref val) => format!("{}", val),
                    etc => format!("{}", etc),
                };

                form.add_text(key, val);
            }
        }

        let file = std::fs::File::open(&file_path).unwrap();
        let file_extension = file_path.extension().and_then(|s| s.to_str()).unwrap_or("");
        let mime = mime_guess::from_ext(&file_extension).first_or_octet_stream();

        form.add_stream(
            parameter_name,
            file,
            file_path.file_name().unwrap().to_str(),
            Some(mime),
        );

        let url = format!("{}/{}", self.api_url, method);
        let form_data = form.prepare().unwrap();

        let response = ureq::post(&url)
            .set(
                "Content-Type",
                &format!("multipart/form-data; boundary={}", form_data.boundary()),
            )
            .send(form_data)?;

        let parsed_response: T2 = serde_json::from_reader(response.into_reader()).unwrap();

        Ok(parsed_response)
    }

mock:

        let server = MockServer::start();

        server.mock(|when, then| {
            when.method(POST).path(path);
            then.status(200).body(response);
        });

Add support for expecting lack of query parameter

it was hard writing a good title,

basically what I want to do is create a mock that only triggers if a specific query parameter is missing:

    mock_server.mock(|when, then| {
        when.method(GET).path("/api")
            .query_param("param", "that-I-want")
            .query_param_i_dont_want("bad-param",no")
        ;
        then.status(200).json_body(json!({
           "response": "So good that you didn't add the bad param!"
        }));

so in this case, a call to /api?param=that-I-want will return a 200 with the message
a call to /api?param=that-I-want&bad-param=no will not return a 200

Feature request: multiple expectations in a single server.

My client needs to fetch user group and it's users from a Keycloak. This requires 3 requests in a single function. I would like to test it by mocking Keycloak using a single server, because my client takes server url in the constructor, so it does not support different host per Keycloak endpoint. Here is my code:

        let server = MockServer::start();
        server.mock(|when, then| {
            when.method(POST)
                .path("/auth/realms/htm/protocol/openid-connect/token")
                .method(GET)
                .path("/auth/admin/realms/htm/groups/uuid1")
                .method(GET)
                .path("/auth/admin/realms/htm/groups/uuid1/members");
            then.status(200)
                .header("content-type", "application/json")
                .body(b_at)
                .status(200)
                .header("content-type", "application/json")
                .body(b_g)
                .status(200)
                .header("content-type", "application/json")
                .body(b_gm);
        });

        let k = keycloak_with_url(server.url(""));
        let res = k.user_grpup(&"uuid1".to_string()).await;

So I would like to define 3 expectations, but they seem to be overwritten. Would it be possible to have a Vec, so one server can serve several endpoints at once? Please correct me if I am doing something wrong or it is already possible (haven't fount in examples).

Panic: Cannot create absolute path from string

Whenever I try to run my test in a debugger, I get the following error

running 1 test
thread 'metadata::tests::test_fetching_src' panicked at 'Cannot create absolute path from string '/var/folders/hf/phjl9q7501gb5wt_qr4ltn3m0000gn/T/.tmpIrQDOX/fake-crate.tar.gz': "environment variable not found"', /Users/user/.cargo/registry/src/github.com-1ecc6299db9ec823/httpmock-0.5.0-beta.1/src/api/mock.rs:1115:63
stack backtrace:
   0: rust_begin_unwind
             at /rustc/18bf6b4f01a6feaf7259ba7cdae58031af1b7b39/library/std/src/panicking.rs:475
   1: core::panicking::panic_fmt
             at /rustc/18bf6b4f01a6feaf7259ba7cdae58031af1b7b39/library/core/src/panicking.rs:85
   2: core::option::expect_none_failed
             at /rustc/18bf6b4f01a6feaf7259ba7cdae58031af1b7b39/library/core/src/option.rs:1221
   3: core::result::Result<T,E>::expect
             at /Users/user/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/result.rs:933
   4: httpmock::api::mock::Mock::return_body_from_file
             at /Users/user/.cargo/registry/src/github.com-1ecc6299db9ec823/httpmock-0.5.0-beta.1/src/api/mock.rs:1115
   5: httpmock::Responders::body_from_file
             at /Users/user/.cargo/registry/src/github.com-1ecc6299db9ec823/httpmock-0.5.0-beta.1/src/lib.rs:1185
   6: cargo_raze::metadata::tests::test_fetching_src::{{closure}}
             at ./src/metadata.rs:389
   7: httpmock::MockServer::mock_async::{{closure}}
             at /Users/user/.cargo/registry/src/github.com-1ecc6299db9ec823/httpmock-0.5.0-beta.1/src/lib.rs:481
   8: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
             at /Users/user/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/future/mod.rs:79
   9: <F as httpmock::util::Join>::join
             at /Users/user/.cargo/registry/src/github.com-1ecc6299db9ec823/httpmock-0.5.0-beta.1/src/util.rs:71
  10: httpmock::MockServer::mock
             at /Users/user/.cargo/registry/src/github.com-1ecc6299db9ec823/httpmock-0.5.0-beta.1/src/lib.rs:456
  11: cargo_raze::metadata::tests::test_fetching_src
             at ./src/metadata.rs:385
  12: cargo_raze::metadata::tests::test_fetching_src::{{closure}}
             at ./src/metadata.rs:342
  13: core::ops::function::FnOnce::call_once
             at /Users/user/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/library/core/src/ops/function.rs:227
  14: core::ops::function::FnOnce::call_once
             at /rustc/18bf6b4f01a6feaf7259ba7cdae58031af1b7b39/library/core/src/ops/function.rs:227
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Repro

I created an example workspace that outlines this issue.
repro.zip

I'm using Vscode and by simply clicking the debug button that appears over tests from the rust-analyzer plugin, I hit this panic.

I'm using 0.5.0-beta.1.

Replace unmaintained difference by similar

The crate difference is unmaintained, I originally saw this in my repository RUSTSEC issue : theredfish/grillon#6

I propose to replace difference by similar which is part of the recommended maintained crates, and also quite popular in comparison of the others.

Please see my PR.

Issues with hard pinning dependencies

Can you actually not hard pin everything? I didn't realize what affect this would have downstream when you first mentioned it in #21

error: failed to select a version for `regex`.
    ... required by package `httpmock v0.5.1`
    ... which is depended on by `cargo-raze v0.6.1 (/Users/user/Code/cargo-raze/impl)`
versions that meet the requirements `=1.3.9` are: 1.3.9

all possible versions conflict with previously selected packages.

  previously selected package `regex v1.4.1`
    ... which is depended on by `cargo-clone-crate v0.1.4`
    ... which is depended on by `cargo-raze v0.6.1 (/Users/user/Code/cargo-raze/impl)`

Multimap header support

Hi, thanks for this good crate.

It should good if we can multiple headers with same name on then same as reqwest HeaderMap

server.mock(|when, then| {
    when.method("POST")
        .path("/foo")
    then.status(200)
        .header(header::SET_COOKIE.as_str(), "mycookie1")
        .header(header::SET_COOKIE.as_str(), "mycookie2")
        .body("resp");
});

Inconvenient MockRef<'a> API

Hi,

I'm trying to write a non-trivial mock server. To do so I'd need to store both the MockServer object and a bunch of MockRef<'a> in the same struct (the one representing my mock server). Unfortunately it's pretty hard to do as MockRef is itself keeping a ref on Server, introducing the well-known self-referential problem.

It is possible to workaround it with tricks such as rental or owning_ref but it greatly increase the code complexity.
An easier approach would be to remove the server borrow in MockRef by wrapping it around a Rc or Arc.
Would you be opened to such change?

Add features to reduce dependencies

With my team we are evaluating httpmock; it really looks like a great project.
Anyway, our test compile-time increased substantially due to its number of dependencies.
Would it maybe be possible to reduce the dependencies using cargo features? For example, I guess isahc and some other "client-side" libraries are used only with a remote server; could they be made optional and enabled with a remote feature?

Question: Are mocked endpoints with same base url mapped to same resource/call?

Hello,

first of all many thanks for that helpful crate. I use it a lot and it's usability and simplicity is awesome.

I came across a problem where I want to test an endpoint with pagination semantics. Here an example:

	let page_1_mock = server.mock(|when, then| {

		when.method("GET")			
			.path("/content");
		then.status(200)
			.header("content-type", "application/json; charset=utf-8")
			.header("NextLink", "http://127.0.0.1:12345/content?page=2")
			.body(json!(
				[
				  {
					  "content": "example1",
				  }
				]
			).to_string());
	});
	
	let page_2_mock = server.mock(|when, then| {

		when.method("GET")			
			.path("/content")
			.query_param("page", "2");
		then.status(200)
			.header("content-type", "application/json; charset=utf-8")
			.body(json!(
				[
				  {
					  "content": "example2",
				  }
				]
			).to_string());
	});	
	
	let result = call_all_pages();
	
	assert_eq!(&result.len(), &2);

This test fails because the second call (extracted from the NextLink header of the first call result) seems to be executed against the page_1_mock again. Is this behaviour expected? I assumed that the calls to the mocks would be distinguished on their complete specification (url, header, query params, ...) but it seems that only the url (path) is the crucial feature.

Maybe I missed something?

Thanks.

Panicking on occasional PoisonError after delete()

Relevant logs:

[2021-11-21T18:12:12Z DEBUG httpmock::server::web::handlers] Deleted mock with id=46
[2021-11-21T18:12:12Z DEBUG httpmock::server::web::handlers] Adding new mock with ID=48
[2021-11-21T18:12:12Z DEBUG httpmock::server::web::handlers] Deleted mock with id=45
[2021-11-21T18:12:12Z DEBUG httpmock::server::web::handlers] Adding new mock with ID=49
[2021-11-21T18:12:12Z DEBUG httpmock::server::web::handlers] Matched mock with id=45 to the following request: HttpMockRequest {

(request details omitted for brevity)

}
thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', /home/charlie/.cargo/registry/src/github.com-1ecc6299db9ec823/httpmock-0.6.4/src/server/web/handlers.rs:133:45
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: PoisonError { .. }', /home/charlie/.cargo/registry/src/github.com-1ecc6299db9ec823/httpmock-0.6.4/src/server/web/handlers.rs:115:40
thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: PoisonError { .. }', /home/charlie/.cargo/registry/src/github.com-1ecc6299db9ec823/httpmock-0.6.4/src/server/web/handlers.rs:115:40

It doesn't happen often, maybe 1 in 10 times that I run my application's tests.

WARN httpmock::server::matchers::targets] Cannot parse json value: EOF while parsing a value at line 1 column 0

If I set up a mock for a GET request:

    pub async fn provision_api(&self) {
        let _get_colo_info = self
            .server
            .mock_async(|when, then| {
                when.method("GET");
                then.body("hi").status(200);
            })
            .await;
    }
    ```
Then send a GET using reqwest:
    ```
    let body = reqwest::get()?
    .text()?;
    ```
I see:
`WARN  httpmock::server::matchers::targets] Cannot parse json value: EOF while parsing a value at line 1 column 0`
It appears this is because it thinks there is a body on the GET request and tries to parse it has json.  Everything appears to work as intended except for the warning prints.

This library depends on OpenSSL

Hi,

it seems like this lib depends on OpenSSL being installed on the host system because of the Isahc crate, which itself depends on OpenSSL. It would be nice to introduce a rustls flag so that users do not need to setup OpenSSL on their build systems.

Update isahc dependency to 1.7.2

A new release of isahc is available since a few days ago, which contains a number of vulnerability related fixes.

Would it be possible to update the httpmock crate so it depends on this version accordingly? Thanks!

Way to print which `Mock`s are registered to a `MockServer`

It would be nice to have a way to see e.g. what URLs are matched or what the rules are. Part of this could just be a Debug impl on Mock that prints the fields of RequestRequirements that are Some in a nice way.

Maybe MockServer could have a fn (&self) -> impl Iterator<Item=&Mock> somehow? I am less sure how those are stored internally.

How to mock the same endpoint but return different requests

I am trying to mock requests to a status endpoint that's polled. The endpoint returns the status of a job. I want to test my logic that I poll the endpoint until the job is complete.

How would I go about doing this?

I have tried:

  1. adding multiple then statements, but methods on then take self by value and so then calls can't be repeated.
  2. adding multiple server.mock(|when, then| { statements, but the mock seems to only return the first one.

latest docker image failing

Hey, thanks for this awesome tool.

I am seeing this error when running with the latest docker image:

$ httpmock --expose true
error: unexpected argument 'true' found

Usage: httpmock [OPTIONS]

For more information, try '--help'.

This does not happen with image tagged alexliesenfeld/httpmock:0.6.2

Simulate server unreachable

I was wondering whether it is currently possible to simulate a MockServer being unreachable so client functionality that deals with such issues can be tested.

I can kind of replicate this by just starting the server whenever I'd like it to be reachable, but stopping it and starting it again using the same address / port is what seems tricky.

Handle url encoding in query param match

Hi, first of all, thanks for the great library. We found an issue with query_param. According to the documentation, it's not necessary to encode the query value.

Sets a query parameter that needs to be provided.
Attention!: The request query keys and values are implicitly allowed, but is not required to be urlencoded! The value you pass here, however, must be in plain text (i.e. not encoded)!

In order to match, we need to replace spaces with +. I'd expect that this library can handle it for me so I don't need to care about encoding internals.

Reference: https://url.spec.whatwg.org/#url-parsing (spaceAsPlus)

let m = server.mock(|when, then|{
    when.query_param("query", "Metallica is cool".replace(" ", "+"));
    then.status(200);
});

let req = hyper::Request::builder()
    .method(Method::GET)
    .uri(
        Url::parse_with_params(
            "http://example.com/",
            &[
                ("query", "Metallica is cool")
            ],
        )
        .unwrap()
        .to_string(),
    )
    .header("content-type", "application/json")
    .body(Body::empty())
    .unwrap();

Add `When::query_unset` for a criterion that a query parameter is not present

First: love this crate. Thank you.

I have a situation where I want a server with two similar mocks: one where a particular query parameter is set; another where it is not. I can make it work by specifying the query-set mock first, but that ordering feels a little fragile. I would love it if there were a way to do something like this:

    let unset = server.mock(|when, then| {
        when.query_unset("foo");
        // ...
    });
    let set = server.mock(|when, then| {
        when.query_param("foo", "bar");
        // ...
    });

If you'd be amenable, I'd be happy to submit a PR for this functionality.

Share variables between requests and responses

I've noticed something missing that would be, IMHO, pretty useful for testing external clients agains mocked servers.

That's sharing variables between requests and responses. For instance, let's say we have a client posting some JSON data to a server, and the server may reply based on that data. The when clause should be able to expose the request data so the then clase can read it and act accordingly.

Optional state reset

According to #6 some users are using a standalone mock server for general API stubbing without the need to run tests with it. The current default implementation always resets the state of the mock server whenever a new test is started. This prevents using httpmock for this kind of stubbing scenarios.

httpmock should have

  • an option to turn off automatic reset and
  • a server method to manually reset the satate on demand

Add `Then::remove_after_n` to self-delete a mock after N hits

Again: love this crate.

I've found some situations where I would like a Then to specify a response (in my case once) and then for a subsequent call with the same parameters to get a different response. One way I could imagine doing it is like this:

    let mock1 = server.mock(|when, then| {
        when.any_request();
        then.status(200).remove_after_n(1);
    });
    let mock2 = server.mock(|when, then| {
        when.any_request();
        then.status(503);
    });

Happy to submit a PR if this seems reasonable. Thanks.

Multiple Mocks isn't working as expected

I'm trying to use the library to test some code that uses a Google API. The endpoint will return a pagination token in each response until there are no more resources to fetch. I was trying to test some code to see if the pagination was working and came across this unexpected behavior. In my test, I have two mocks with the same path, but different query string parameters -- here is a simplified version.

#[test]
fn test_different_qs_responses() -> Result<(), Box<dyn std::error::Error>> {
    let server = MockServer::start();

    let mock_first = server.mock(|when, then| {
        when.method(GET)
            .path("/v1/mediaItems")
            .query_param("pageSize", "3");
        then.status(200)
            .header("Content-Type", "application/json")
            .json_body(json!({
                "mediaItems": [],
                "nextPageToken": "the_next_page"
                }));
    });

    let mock_last = server.mock(|when, then| {
        when.method(GET)
            .path("/v1/mediaItems")
            .query_param("pageSize", "3")
            .query_param("pageToken", "the_next_page");
        then.status(200)
            .header("Content-Type", "application/json")
            .json_body(json!({
                "mediaItems": [],
                }));
    });

    let client = reqwest::blocking::Client::new();
    let mut query = vec![("pageSize", "3")];
    
    // first
    client.get(&server.url("/v1/mediaItems"))
          .query(&query)
          .send()?;
    
    query.push(("pageToken", "the_next_page"));

    // last
    client.get(&server.url("/v1/mediaItems"))
    .query(&query)
    .send()?;
    
    mock_first.assert();
    mock_last.assert();
    Ok(())
}

I'd expect mock_first to match the first request and mock_last to match the last since the query parameters specified in the match and in each request are different, but that doesn't appear to be the case. When I run the code I get this error:

thread 'test_different_qs_responses' panicked at 'assertion failed: `(left == right)`
  left: `2`,
 right: `1`: The number of matching requests was higher than expected (expected 1 but was 2)', C:\Users\likar\.cargo\registry\src\github.com-1ecc6299db9ec823\httpmock-0.6.5\src\api\mock.rs:207:13

Do I have a misunderstanding of how multiple mocks can be used or is this a bug in how mocks match requests?

Update to hyper >=1.0

It would be great if dependency on hyper can be updated (to >= 1.0, ideally 1.1). This causes quite a big set of dependencies in my project stuck with older versions.

Redirecting function's requests to mock server

Hi,
I want to be able to test a function that does not have the server's url as one of it's arguments. For example - if I have a function like eval_code(code: String) which uses (directly or through yet another function) e.g reqwests POST requests in its body, and let's say the requests are being sent to https://example.com/endpoint - then I want to be able to redirect those requests to the mock server in my tests, automatically.
So theoretically what I want to have is define a mock server on some real address - and then, each time a request/response is being sent to/from that address it will automatically be redirected to my mock server, allowing me to test the functions that interact with that address.
I hope my explanation was clear enough, and maybe that's even possible right now - I just couldn't find a way to do it.

DELETE can have a body

Hello,

Thanks for the library. It's helping me a lot!

At the moment I am working with an API that expects a body with a DELETE request. As far as I can tell a DELETE request is allowed to have a body, but since it's listed in the NON_BODY_METHODS here I can't use the fluent API to define the mock. Could delete be removed from that list?

This is not urgent because I can do a workaround with custom request matcher.

RFC: Portability/Relative Path - Then body_from_file Into type String

Error and/or Documentation Error and/or Relative Path Handling Error

I was using a relative path for the fn call directly per doc example for Then::body_from_file

But environment variable not found error popped up instead:

Cannot create absolute path from string 'x':  "environment variable not found"

I tried to look whether the doc said much about environment variables but nothing to evaluate.

However Looking into I can find the call to crate::util::get_test_resource_file_path() when path.is_absolute is false:

match env::var("CARGO_MANIFEST_DIR") {

Problem is this env may not be visible to cargo sub-commands as I found the hard way with tarpaulin:

env RUN_MODE=development cargo +nightly tarpaulin --run-types Tests,Doctests,Benchmarks,Examples,Lib,Bins -v
  • Works okay with cargo test but tarpaulin sub-command seems to omit that environment variable.
  • Omitting the env seems to be the case with cargo sub-command.
  • Cargo documentation only lists one env passed down without explicitly ruling out others.

Doc wise Mock::return_body_from_file says explicitly either relative/absolute:

resource_file_path - The file path to the file with the response body content. The file path can either be absolute or relative to the project root directory.

As for relative path definition, I would expect this to be the OsStr<> from the Current working directory (CWD) which may be set to be elsewhere than the directory holding the cargo manifest.

For backwards compatibility the document probably should say that the environment variable is used for relative path or behavior could be changed some way to rely on CWD that would break things relying on it to be the manifest path

Plus CWD typically is not UTF-8 guaranteed path if Into is relied (not absolutely sure whether CARGO_MANIFEST_DIR is guaranteed to be either for lossless conversion Into either if it's allowed to be non UTF-8 🔢 ?)

I personally use this pattern and document the var_os OssStr returning environment variable use for my app:

  let mut path_locator = match env::var_os("APP_CONFIG_PATH") {
      Some(val) => PathBuf::from(val),
      None => env::current_dir().unwrap()
};

If I feel I need to enforce UTF-8 paths like Rust ecosystem like cargo does I use camino Utf8Path to see if I can upgrade from Path and match appropriate error.

Happy to submit a PR to fix doc and automatically check the truth whatever is decided when/if there is a decision what to do with this.

Code Blocks Involved

Then::body_from_file(httpmock/0.5.8/source/src/lib.rs)

pub fn body_from_file<S: Into<String>>(self, body: S) -> Self {
        self.mock.set(self.mock.take().return_body_from_file(body));

Mock::return_body_from_file(httpmock/0.5.8/source/src/api/mock.rs):around 1317

 pub fn return_body_from_file<S: Into<String>>(mut self, resource_file_path: S) -> Self {
        let resource_file_path = resource_file_path.into();
        let path = Path::new(&resource_file_path);
        let absolute_path = match path.is_absolute() {
            true => path.to_path_buf(),
            false => get_test_resource_file_path(&resource_file_path).expect(&format!(
                "Cannot create absolute path from string '{}'",
     //--snip--

Dilemma

  • Documentation advertises/promises both the relative and absolute path use
  • Relative path is implicitly derived from documentation as current working directory (CWD)
  • If relative path is used CARGO_MANIFEST_DIR is used as base path which may or may not be lossless Into
  • Cargo may or may not pass this env down to sub-command
  • std::String is always UTF-8
  • std::ffi::OsString implements Into String type but is lossy and breaks relative path guarantee
  • OsString and Path is supposed to work everywhere safely abstracting it
  • Environmental variable etc. can be non-UTF8 and requires appropriate handling boilerplate before it hits Then::body_from_file
  • Lossy conversion is due to POSIX filenames allowing anything except \0
  • Requires conversion to String for the whole path (if I read the code right🙆‍♀️)
  • String/std::path::Path conversion has been always a pain
  • Many OS allows wide array of bytes in OsStr

Solution 1: Allow From &std::path::Path without using CARGO env

Pros:

  • Straightforward pattern is to pass std::path::Path directly e.g. with config::from
  • Allows relative path handling at ease over "somebody else handles it" abstraction
  • Library already uses std::Path internally despite Into String
  • Library using this can handle easily cross-platform TEST_DATA path which may contain "" or "/" path separators etc.
  • The world is not perfect problem is easier - less friction to use the library esp re: relative path
  • Documentation is the source of truth, though implicit Type impl loses (String is only UTF-8)
  • Doesn't rely on cargo env

Cons:

  • Rust configured paths are UTF-8
  • Lossy Display/Debug
  • Either confusing implementation if String is still allowed
  • Libs using may/will break if on same method - needs new method
  • Current working directory derived from var_os needs to be used instead of CARGO env

Example is using config::File::from

impl<'a> From<&'a Path> for File<source::file::FileSourceFile> {
    fn from(path: &'a Path) -> Self {
        File {
            source: source::file::FileSourceFile::new(path.to_path_buf()),
            //--snip--

Where I can easily construct and pass the full Path natively and use relative path if I want

let mut path_locator = match env::var_os("CONFIG_PATH") {
  Some(val) => PathBuf::from(val),
  None => env::current_dir().unwrap()
};

path_locator.push("subdir");

// And then finally merge the file I want with full POSIX compatible Path

s.merge(File::from(path_locator.join("default")).required(false))?;

Solution 2: Enforce and be explicit about UTF-8 and handle Error thru camino

Pros:

  • Proper taint check and error
  • Lossless Display/Debug
  • Less friction for anyone who uses the library as it's checked for validity
  • Does not need new method as it was always UTF-8
  • Document implicit Type impl wins to some degree (String is only UTF-8 and we are just enforcing that)

Cons:

  • Relative path use is a pattern sadly good or bad.
  • Friction for anyone who uses the library
  • Library using this have to handle cross platform paths for TEST_DATA path which may contain "" or "/" path separators etc.
  • Adds camino crate that handles sanity on Utf8Paths
  • Documentation which advertised flexibility between relative/absolute loses

Solution 3: New fn pass by Buffer/Stream/Channel oneshot etc.

Pros:

  • Many async libs just wait-read the whole buffer internally and then process/pass the whole of it
  • Would allow timing and replaying a server behaviour in timeline basis for writing into kernel buffer out
  • Implementation could say replay sequence from libpcap file the near timing packets were either sent/recvd
  • Could differentiate between Content-Length, gzip stream, chunked modes
  • WebSocket etc. streaming
  • Can serve as building block for HTTP/2 or gRPC/Protobuffers
  • More fun than simple singular delay :)

Cons:

  • Library using this have to handle cross platform paths for TEST_DATA path which may contain "" or "/" path separators etc.
  • Library was never intended for buffer/stream handling?
  • Complexity

Solution 4: Status quo

Pros:

  • Supposed to have less burden for library

Cons:

  • Friction to use the library
  • Library using this have to handle cross platform paths for TEST_DATA path which may contain "" or "/" path separators etc.
  • Misleading error (library is not using environment variable, the full path is passed to it)
  • Still using lossy Display/Debug as it is using Path internally

Solution is probably somekind combination?

I usually do both integration / units along my /// docs or even on README.md that gets included in test run and tarpaulin --run-types DocTests takes those into account

#[cfg(doctest)]
doc_comment::doctest!("../README.md");

I will check with tarpaulin too to expose the environment var but it would be nice to get some clarity as it caused a bit confusion initially for me and not sure if there is anyone doing things without cargo env :)

  • pinkforest(she/her/hers)

Ability to specify a mock's port

https://github.com/oxidecomputer/progenitor recently added the ability to generate httpmock's for an OpenAPI spec. This is great for testing.

However it would also make it quite easy to build a reasonable mock service that can be used instead of a "real" service. The only problem is that httpmock cant be told which port to use. Well, it can, but only from within the crate, which is what the standalone feature uses.

It would be nice if server::start_server was public outside the crate, so I could build my own "standalone", using my generated openapi mocks and some custom code to return appropriate responses.

However any other mechanism for being able to start the mock server at a specified port would likely be sufficient for my purpose.

GRPC support

Hello,

Is there a reliable way of using this to test GRPC calls?

Perhaps by providing a layer to parse a proto and translate first messages to json, use the current implementation and translate back the response?

Trying to understand if this could be realistically implemented in a easy form.

Thanks,
Marlon

Expose base URL on MockServer

I am implementing a REST client, and using httpmock to test it. I instantiate my client by passing the address of the server (base URL), which is an HTTP(S) URL, like so:

cl = MyClient::new("http://localhost:8042")

Then my client implements various methods, that send HTTP requests to various paths, relative to the base URL. In each of my tests, in order to instantiate my client, I have to compile the base URL manually every time:

let mock_server = MockServer::start();
let url = format!("http://{}:{}", &mock_server.host(), &mock_server.port());

cl = MyClient::new("http://localhost:8042")

// this does format!("{}/{}", self.base_url, "/patients"); under the hood
let resp = cl.list_patients();

Not such a big deal really, it's only one line, but I thought that for convenience MockServer could expose the base URL in a method. For example by making the path argument in url function optional (if path is None, return the base URL).

If that makes sense, I'd be more than happy to make a PR. I am very new to Rust, so it could be that the way I am designing my client (or the way I am testing it) is suboptimal. I'd appreciate an advice on how to do it the right way :)

Asserting the absence of body, headers, etc

I would like to assert that my HTTP request body is empty or that a certain header doesn't exist. Is this something that httpmock supports at the moment?

Currently, I can see there the following functions exist:

  • header_exists()
  • query_params_exists()
  • cookie_exists()

It would be nice if an opposite version of them existed plus one for checking if the body is empty.

Won't compile after regex 1.9.0

error[E0432]: unresolved import `regex::internal`
  --> /home/runner/.cargo/registry/src/index.crates.io-6f17d22bba15001f/httpmock-0.6.7/src/server/mod.rs:19:12
   |
19 | use regex::internal::Input;
   |            ^^^^^^^^ could not find `internal` in `regex`

See: rust-lang/regex#978

unable to build with libressl 3.4.1

libressl 3.4.0 seems the latest supported but 3.4.1 is out and is in use by my platform/os. is it possible the supported version can be bumped please?

Mocking retries?

I'm trying to mock a service that is supposed to be called multiple times and fail, and then succeed at the Nth time. Since these are retries the requests should be the same, so there's no way to distinguish them by parameters or by path/body.

Is that something that can be done with httpmock?

Overwrite mocks

I'd like the ability to overwrite a previously defined mock if I define a new one with the same "when" like this:

self.mock_server.mock(|when, then| {
when.method(GET)
.path("/api/"));
then.status(200).json_body(json!({
"msg": "A"
}));
});

now a call to /api returns msg: A

then I do
self.mock_server.mock(|when, then| {
when.method(GET)
.path("/api/"));
then.status(200).json_body(json!({
"msg": "B"
}));
});

now a call to /api returns msg:B

currently it seems like the only way is to delete the old mock first which is a bit cumbersome imo

Error from using regex::internal::Input in code

httpmock version: 0.6
rust version: rustc 1.73.0-nightly (39f42ad9e 2023-07-19)

Current Behavior:

Screenshot from 2023-07-20 09-43-05

Work around: Removing use regex::internal::Input from src/server/mod.rs file fixed the problem

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.