We can use cy.server()
and cy.route(...)
to mock HTTP calls.
The exact same thing for Websockets.
For apps using websockets quite heavily this would come really handy.
Might be related to https://github.com/cypress-io/cypress/issues/199
Would be great to have the ability to watch a bi-directional web socket to process messages from the back-end as they are received.
Use Case:
User preforms a action through the front-end which actions several back-end events. It would be great if we could monitor that these different events did occur (there is an endless amount of different events and combinations of events).
What would be great if there was a simple message pool/queue or a way to "monitor" a bi-direcitonal websocket and capturing the messages received from the back-end.
We would then write tests to check if those events are being executed by looking at the socket/pool/queue and seeing if the events were received after X user actions.
Hi
Any news about this feature ?
Thanks
@EmmanuelDemey you can already mock WebSockets in Cypress (as a workaround) the same way you do it for fetch, using onBeforeLoad
in visit. I use the mock-socket
library:
onBeforeLoad(win) {
// Call some code to initialize the fake server part using MockSocket
cy.stub(win, "WebSocket", url => new MockSocket.WebSocket(url))
}
@EmmanuelDemey you can already mock WebSockets in Cypress (as a workaround) the same way you do it for fetch, using
onBeforeLoad
in visit. I use themock-socket
library:onBeforeLoad(win) { // Call some code to initialize the fake server part using MockSocket cy.stub(win, "WebSocket", url => new MockSocket.WebSocket(url)) }
How did you solve the issue with sockjs-node
?
Which issue? You just need the mock-socket
library to get it work.
@quentinus95
I would be interested in seeing a full example of your implementation of the MockSocket Library. I'm struggling to get it to work correctly. (partly because I was trying to use the RxJS WS Subject)
In your example, you are passing the URL from the window object it looks like is that right?
Yes. a full example should be very helpful.
I have an example that is almost working the way I want it to now. Having an issue with the Server I believe. for some reason, it will not close and tries to reconnect and cypress keeps picking up xhr requests so it doesn't close.
However, I was able to stub out the client web socket correctly.
cy.visit('/', {
onBeforeLoad: (win) => {
cy.stub(win, 'WebSocket', (url) => {
MockServer = new Server(url)
MockServer.on('connection', (socket) => {
socket.send(alert)
setTimeout(() => {
socket.close()
MockServer.close()
}, 1000)
})
return new WebSocket(url) /* mock socket */
})
}
})
Here is a more complete example. I've been using this for a year to test applications without any issue.
In a server.js
file (for instance):
const sockets = {}
export function initServer() {
// useful to reset sockets when doing TDD and webpack refreshes the app
for (const socket of Object.values(sockets)) {
socket.close()
}
mockServer()
}
function mockServer() {
// Of course, your frontend will have to connecto to localhost:4000, otherwise change this
sockets.mockServer = new Server("ws://localhost:4000")
sockets.mockServer.on("connection", socket => {
sockets.server = socket
// Will be sent any time a client connects
socket.send("Hello, world!")
socket.on("message", data => {
// Do whatever you want with the message, you can use socket.send to send a response
}
}
}
You can also use setInterval to send events regularly, and even export some functions to trigger an event from the server in a test.
You then just have to stub websocket in the visit Cypress method:
import { initServer } from "./server.js"
cy.visit("/", {
onBeforeLoad(win) {
initServer()
cy.stub(win, "WebSocket", url => new MockSocket.WebSocket(url))
}
})
That's all.
@theAndrewCline thanks for that example, I've got a test working but it only works for one test, then it says that the mock server is already listening on that url:
describe('mock socket method 1', () => {
let mockSocket;
let mockServer;
beforeEach(() => {
cy.visit('/', {
onBeforeLoad(win: Window): void {
// @ts-ignore
cy.stub(win, 'WebSocket', url => {
mockServer = new Server(url).on('connection', socket => {
console.log('mock socket connected');
mockSocket = socket;
});
if (!mockServer) return new WebSocket(url);
});
},
});
});
afterEach(() => {
mockSocket.close()
});
it('gets a message', () => {
const object = _createSettingsApiPutPayload(defaultSettingsState)
mockSocket.send(JSON.stringify(object));
cy.contains('Motion threshold')
});
it('gets a message', () => {
const object = _createSettingsApiPutPayload(defaultSettingsState)
mockSocket.send(JSON.stringify(object));
cy.contains('Motion threshold')
});
});
If I change the method to before()
instead of beforeEach
it works, but then I don't get a fresh environment for each test. I tried mockSocket.close()
in afterEach()
as you can see, but that doesn't work.
@tuzmusic you're only closing the socket but not stopping the server, have you tried to call .stop()
on your server instance?
do we have to use cy.visit('/', {...}) and not specific url inside my app? why?
do we have to use cy.visit('/', {...}) and not specific url inside my app? why?
We don't: you can set the baseUrl
in your cypress.json
file to the address where your app is being hosted (http://localhost:3000
for example) and then cy.visit
uses paths relative to that.
So cy.visit('/')
becomes equivalent to cy.visit('http://localhost:3000/')
and cy.visit('/some-other-page.html')
becomes equivalent to cy.visit('http://localhost:3000/some-other-page.html')
.
Without the baseUrl
you'd need to include those entire urls.
I have an example that is almost working the way I want it to now. Having an issue with the Server I believe. for some reason, it will not close and tries to reconnect and cypress keeps picking up xhr requests so it doesn't close.
However, I was able to stub out the client web socket correctly.
cy.visit('/', { onBeforeLoad: (win) => { cy.stub(win, 'WebSocket', (url) => { MockServer = new Server(url) MockServer.on('connection', (socket) => { socket.send(alert) setTimeout(() => { socket.close() MockServer.close() }, 1000) }) return new WebSocket(url) /* mock socket */ }) } })
@tuzmusic I am trying to use the same code snippet, in my code, but I always get this error Server not defined
.
Just my two cents, this custom cypress command for graphQl websocket mocks works for me:
import { Server, WebSocket } from "mock-socket"
const mockGraphQlSocket = new Server(Cypress.env("GRAPHQL_WEB_SOCKET"))
Cypress.Commands.add("mockGraphQLSocket", mocks => {
cy.on("window:before:load", win => {
win.WebSocket = WebSocket
mockGraphQlSocket.on("connection", socket => {
socket.on("message", data => {
const { id, payload } = JSON.parse(data)
if (payload && mocks.hasOwnProperty(payload.operationName)) {
mocks[payload.operationName](
// Delegate the socket send call to the party owning the mock data since multiple calls might need to be made (for example to update multiple individual entries)
data =>
socket.send(
JSON.stringify({
type: "data",
id,
payload: { errors: [], data }
})
),
payload.variables
)
}
})
})
})
})
And then the command could be used something like this (depending on the query requirements)
cy.mockGraphQLSocket({
yourGraphQlQuery: (send, { uuids }) => {
uuids.forEach(id => {
send({
yourGraphQlQuery: {
...yourMockData,
id
}
})
})
}
})
@BobD looks nice. How do you close you mock server though?
@theAndrewCline Good question, haven't though about that.
I guess the mock-socket close()
can be used for that but i haven't hit a use-case where i needed that in my tests (yet) https://www.npmjs.com/package/mock-socket#server-methods
I gave this another go today based on few comments above, using MockSocket
. Absolutely no luck so far. Has anyone something to add that I could have missed or another solution?
Thanks
Before instantiating a new Server
, check if there's an existing one. If one exists, close it:
if (mockServer) {
mockServer.close();
}
mockServer = new Server('ws://localhost:8080');
Does anyone work with AWS (AppSync) implementation of WebSockets? I can't connect mock, every time I'm getting an error: WebSocket connection to {url} failed
Anyone had any luck with mocking socket.io?
I was not able to intercept the ws using mock-socket approach, which is to add window.io = SocketIO;
.
@erezcohen I managed to mock socket.io using socktet.io-mock and custom commands.
Test
cy.mockSocketIO();
cy.visit("/");
cy.pushSocketIOMessage("chat", "Hello World");
Commands
import SocketMock from "socket.io-mock";
let socket = new SocketMock();
Cypress.Commands.add("mockSocketIO", (mocks) => {
cy.on("window:before:load", (window) => {
window.io = socket;
});
});
Cypress.Commands.add("pushSocketIOMessage", (event, payload) => {
socket.socketClient.emit(event, payload);
setTimeout(() => {}, 1000);
});
Application
if (window.io) {
this.socket = window.io;
} else {
this.socket = io("/");
}
I am not happy with the application part. But could not find another way to mock the socket.io-client.
function mockServer() { // Of course, your frontend will have to connecto to localhost:4000, otherwise change this sockets.mockServer = new Server("ws://localhost:4000") sockets.mockServer.on("connection", socket => { sockets.server = socket // Will be sent any time a client connects socket.send("Hello, world!") socket.on("message", data => { // Do whatever you want with the message, you can use socket.send to send a response } } }
@quentinus95 : Where is new _Server_() being defined in this example? Is the a specific dependency you need?
@edjumacator you can import it from the mock-socket
library.
Most helpful comment
@quentinus95
I would be interested in seeing a full example of your implementation of the MockSocket Library. I'm struggling to get it to work correctly. (partly because I was trying to use the RxJS WS Subject)
In your example, you are passing the URL from the window object it looks like is that right?