LukeMathWalker / wiremock-rs

HTTP mocking to test Rust applications.
Apache License 2.0
617 stars 70 forks source link

async `respond_with` / `Respond` #84

Open johannescpk opened 2 years ago

johannescpk commented 2 years ago

Thanks for wiremock! It seems that it's currently not possible to execute async code in the respond method or respond trait. It'd be useful to have it natively supported by wiremock.

Wasn't able to find a workaround, tried

.respond_with(move |req: &Request| {
        let handle = tokio::runtime::Handle::current();
    std::thread::spawn(move || {
        handle.block_on(async {
            // Some async call
        });
    })
    .join()
    .unwrap();

    ResponseTemplate::new(200)
})

but that's blocking the tokio runtime since the test runs as #[tokio::test].

LukeMathWalker commented 2 years ago

What kind of async tasks are you trying to perform in response_with?

johannescpk commented 2 years ago

It's some business logic, in this case it's tower::util::Oneshot calls.

LukeMathWalker commented 2 years ago

I'll need an example a bit more concrete to understand if it makes sense to extend the library or not 😄

johannescpk commented 2 years ago

Personally I'd say it'd make sense to have callbacks be async in general unless there's a good reason to not do it. Reason being that it's easy to have a sync wrapper in an async context but it's hard the other way around.

In this specific case I'm testing the correct setup of a FHIR Subscription. The HTTP request flow looks like this. The relevant part is 9. and 10. where a handshake takes place.

Basically:

Client --[ HTTP POST Create Subscription ]--> Server
Server --[ HTTP POST Handshake ] --> Client

The library exposing the functionality has an async method create_subscription which should only return once the subscription was created in full, with a successful handshake. To test this I have two MockServer instances, one being the client and one being the server. The test then calls create_subscription and only after the subscription mock finished (the server mock returning the ResponseTemplate) it should do a mocked handshake call to the client so create_subscription returns. The communication inside the client to signal that the handshake arrived is made with tokio::Notify. In the tests I'm now also using Notify as a workaround (requires me to juggle a tokio::join! later in the test), but I'd like to directly use https://github.com/tokio-rs/axum/blob/main/examples/testing/src/main.rs#L64-L67 which is async.

rminderhoud commented 1 year ago

Hey there, thanks for this great library! I recently ran into the same issue. Trying to call some async code from a custom responder. Basically, I am trying to create a mock to validate an HTTP payload and one of the libraries to parse multipart data is async. e.g.

struct CustomResponder;

impl wiremock::Respond for CustomResponder {
    fn respond(&self, req: &wiremock::Request) -> wiremock::ResponseTemplate {
        // ... some logic ...

        // This is an async function so can't be used here
        let multipart = multer::Multipart::with_reader(req.body.as_slice(), boundary);

        // ... validate multipart data ...
    }

A lot of http libraries have gone async so it would be useful to be able to use them

moh-eulith commented 1 year ago

Just another example in case it helps: In the response method, I have to call another service (another mock which implements json rpc), which is async.