Here, at Lenses.io, we put great emphasis on testing all parts of our applications, from the UI to API’s, using a wide range of tests, starting with unit tests and up to integration, E2E, acceptance, etc.

One of the tools we intensively use to achieve this is Cypress, which although was created for UI E2E tests, supports also API testing via it’s cy.request command. Taking it further, the 2 types of tests can be mixed, by using shared code, in order to speed up the time needed to run a full suite of UI Integration/E2E tests (that are normally quite slow), but this topic could be the subject of a future post, as there’s a lot more to be said on this.

While building one of the testing frameworks for our APIs (part of the integration & E2E), we realised that Cypress does not offer support for testing WebSocket endpoints out of the box, the same as it does for the REST ones. And when dealing with streaming data, accessing and testing WebSockets is critical. Even when you need to test multiple WebSocket connections via the UI, there are certain limitations, that require workarounds.

Yes, it can be done!

Fortunately, one of the great things about Cypress, is it’s extensibility, so instead of trying to find a new tool for the job, we decided to write our own plugin, which we open sourced, in order to help other developers who are in the same situation. Before doing that, we extracted our business logic so feel free to extend it with yours.

The library has only 1 dependency (besides Cypress obviously), which is rxjs, an obvious choice for complex transformations and manipulations of data streams.

In order to support a variety of cases we added two commands that I will be describing below:

First command, cy.streamRequest , is intended to support the common cases out of the box and returns an array of the items that were pushed by the WebSocket connection, up to the point when the connection was closed. This will happen once a condition (customisable via the StreamRequestOptions ) has been met. It also has a retrial mechanism, if for example the data you were waiting was not ready yet, which can be the case for complex systems, where it might take a few seconds from the moment data is inserted (via a REST call for example), processed and pushed through different systems and then via WebSocket to the Client. (Cypress provides similar retry functionality when using cy.request , but using recursive calls instead)

, is intended to support the common cases out of the box and returns an array of the items that were pushed by the WebSocket connection, up to the point when the connection was closed. This will happen once a condition (customisable via the ) has been met. It also has a retrial mechanism, if for example the data you were waiting was not ready yet, which can be the case for complex systems, where it might take a few seconds from the moment data is inserted (via a REST call for example), processed and pushed through different systems and then via WebSocket to the Client. (Cypress provides similar retry functionality when using , but using recursive calls instead) Second command, is a low level one, cy.stream, that only returns a webSocketSubject. This can be extended to suit different custom cases, using the full capabilities of rxjs, such that the data can be transformed using your own business logic requirements before it is returned for assertion.(you can use the streamRequest as a reference on how to achieve this) At the same time, as the result is a subject, multiple ones can be merged into one, for cases when you need to test multiple WebSocket connections at the same time. The downside is that the subjects need to be subscribed manually and the done() method called during the complete callback such that Cypress knows the test is over. (see examples in the repo)

As the commands are written in TypeScript, you have full types support out of the box, in order to tell the test what the type will the returned messages be. Let’s have a look at an example to see how we would use the streamRequest command:

First we set up a few of the options:

takeWhileFn: when a message of type ‘END’ will be streamed, the connection will close, and return the array of messages that arrived so far

retryUntilFn: this will take the array above and test that it contains a message with the data property higher than 5 (it’s randomised on the server). This is useful in cases where the backend might take some time to stream the correct data, so you need to have the test autorepeating

startupMessage: self explanatory, message sent on connection open, usually a login, or request message of some sort

Second, the command is wrapped by cy.wrap. The reason for this is that Cypress will, by default timeout after 4 seconds. There are 2 way currently to go around this: First, using the cy.wrap, or by modifying the defaultCommandTimeout in the cypress config. Further investigation is required in order to try to modify the internals of Cypress such that it uses the streamTimeout property instead.

Then we make the request using cy.streamRequest and tell the TS compiler what type are the messages coming back from the server. This will make the tests more stable as we will be testing against a predefined interface.

You can find the project here with some more examples inside and details about the various other options that you can set on the streamRequest command.

Hope this helped, wishing you a green build!

About us:

Lenses provides a monitoring, self-service administration, governance & secure data access layer on any Kafka & Kubernetes. It allows developers to build, deploy, debug & manage streaming flows in a fraction of the time with reduced risk and more consistency. See our Engineering Blog or Twitter for more articles on Kafka and DataOps.

We are hiring, so be sure to check out our careers page.