Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider support for mocking websockets #113

Open
blueridanus opened this issue Apr 11, 2023 · 10 comments
Open

Consider support for mocking websockets #113

blueridanus opened this issue Apr 11, 2023 · 10 comments

Comments

@blueridanus
Copy link

Would this addition be welcome? I could look into writing a PR for this.

@LukeMathWalker
Copy link
Owner

I'd like to understand the design before going through a PR!
I'm definitely interested in the feature 😄

@blueridanus
Copy link
Author

blueridanus commented Apr 12, 2023

Cool! Not entirely sure on design myself, but here's one possibility (for the user-facing part at least):

use wiremock::ws::*;

Mock::given(ws::Upgrade)  // new kind of matcher
    .and(path("/hello_ws"))
    // this could simply echo ws messages back to client. could also have another case which takes a closure
    .respond_with_ws(ws::EchoResponder)
    .mount(&mock_server)
    .await;

@LukeMathWalker
Copy link
Owner

What does ws::EchoResponder look like in terms of implementation? What trait is it implementing?

@xd009642
Copy link
Contributor

xd009642 commented Apr 12, 2023

I would imagine you want a separate Respond trait like StreamingRespond which looks something like:

#[async_trait]
pub trait StreamingRespond: Send + Sync {
    // Required method
    async fn respond(&self, request: &Request, rx: Receiver<Bytes>, tx: Sender<Bytes>) -> ResponseTemplate;
}

This might then be generic enough to allow for mocking grpc servers as well (exercise left to reader). I figured normal ResponseTemplate can still be returned because the upgrade may be rejected and a HTTP response like unauthorised may be returned in some instances (or for grpc you always end up with a http status code at the end anyway).

The choice of channels to use and whether you use/expose Bytes via the public API may need more work and then how it's injected into the mock but that's my initial gut instinct (hopefully not completely off base). Also async-trait is naturally another choice being made here which would be another dependency added into the mix (hopefully not too egregious of one)

@blueridanus
Copy link
Author

blueridanus commented Apr 12, 2023

I figured something similar, but WebSockets specific (e.g. WsRespond). I think the more generic approach above could be even better, as long as some attention is paid to the possibility of Transfer-Encoding: chunked HTTP requests (which that trait name seems to suggest).

@xd009642
Copy link
Contributor

xd009642 commented Apr 12, 2023

Yeah, I think for other things such as websockets or grpc you would need to bring in a library like tungstenite and tonic for the transport stuff that's not as visible to user (i.e. websocket handshaking etc). So they'd have to be optional dependencies (no need to add bloat for people).

I guess that means adding some sort of transport style middleware to handle serializing/deserializing to things like websocket TCP frames and also doing any handshaking etc that has to be done. Maybe it would look a bit like:

use wiremock::ws::*;

#[async_trait]
impl StreamingRespond for Streamer {
   // left as an exercise to the reader
}

Mock::given(ws::Upgrade)  // new kind of matcher
    .and(path("/hello_ws"))
    .transport(transport::Websocket) // Specify what transport will be used to send the bytes over
    .streaming_respond_with(Streamer::default()) // Specify how data received from transport will be handled
    .mount(&mock_server)
    .await;

@blueridanus
Copy link
Author

blueridanus commented Apr 12, 2023

An advantage of forgoing the generic approach would be handlers/implementors getting typed messages (e.g. Text(..), Binary(..), Ping(..) etc for WS) instead of raw bytes, although this could potentially be addressed by making the StreamingRespond trait even more generic with an associated message type.

@LukeMathWalker
Copy link
Owner

I lean towards having the interface being specific to websockets—we can always generalize later if/when we decide to support something like gRPC (which hasn't been the case for the past ~3 years).
Especially if a special-cased interface allows us to provide better ergonomics, which is the paramount focus in wiremock—writing tests is annoying enough.

@xd009642
Copy link
Contributor

xd009642 commented Apr 12, 2023

Sure thing, I was running ahead to testing one of our gRPC services as well as the websocket ones 😅

I was also thinking from the generics on StreamingResponse how it would turn into a tower template soup so probably the best approach tbh

@dpezely
Copy link

dpezely commented Apr 13, 2023

I can help if you need early adopters to test and contribute based upon some familiarity with WebSockets as well as using wiremock.

(Currently working on a project that could use this, and we're already using wiremock thus would like to keep it all in same the family. Thank you for doing this!)

Edit: fixed typo.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants