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

Unable to use in successive Cypress tests #288

Open
tuzmusic opened this issue Dec 3, 2019 · 3 comments
Open

Unable to use in successive Cypress tests #288

tuzmusic opened this issue Dec 3, 2019 · 3 comments

Comments

@tuzmusic
Copy link

tuzmusic commented Dec 3, 2019

Problem

I am trying to use mock-sockets with Cypress, setting up the mock in the onBeforeLoad hook for cy.visit() in my beforeEach block. I can get one test to work but when the mock setup runs on the next beforeEach I get an error that A mock server is already listening on this url.

code under test:

(called from my React app's componentDidiMount)

  subscribeToSettings(url: string): W3CWebSocket {
    let settingsSubscription = new W3CWebSocket(url);
    settingsSubscription.onopen = () => console.log('WebSocket Client Connected (settings)');
    settingsSubscription.onclose = () => console.log('WebSocket Client Disconnected (settings)');
    settingsSubscription.onmessage = (message: MessageEvent) => this.handleSettingsMessage(message);
    return settingsSubscription;
  }
  
  /**
   * Handler for websocket settings messages, which updates the local settings values.
   * @param message the websocket message
   */
  handleSettingsMessage(message: MessageEvent) {
    const updatedValues = JSON.parse(message.data);
    console.log('A message was received on the settings channel.', updatedValues);
    this.props.updateSettingsFromBackend(updatedValues);
  }

cypress tests

import { Server } from 'mock-socket'
import { defaultSettingsState } from "../../src/reducers/settings.reducer";
import { _createSettingsApiPutPayload } from "../../src/actions/settings.actions";

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)
          mockServer.on('connection', socket => {
            console.log('mock socket connected');
            mockSocket = socket;
          });
          mockSocket = new WebSocket(url);
          return mockSocket
        });
      },
    });
  });
  
  afterEach(() => {
    mockSocket.close()
    mockServer.stop()
  });
  
  it('gets a message', () => {
    cy.contains('SETTINGS').click()
    const object = _createSettingsApiPutPayload(defaultSettingsState)
    mockSocket.send(JSON.stringify(object));
    cy.contains('Motion threshold')
  });
  it('gets another message', () => {
    cy.contains('SETTINGS').click()
    const object = _createSettingsApiPutPayload(defaultSettingsState)
    mockSocket.send(JSON.stringify(object));
    cy.contains('Motion threshold')
  });
});

Here are the logs from my console:

WebSocket Client Connected (settings)
mock socket connected at url ws://localhost:8702/PM_Settings
A message was received on the settings channel. {…}
mock socket connected at url ws://localhost:3000/sockjs-node/949/mhuyekl3/websocket
The development server has disconnected.
Refresh the page if necessary.
Uncaught Error: A mock server is already listening on this url

I wonder if it has to do with that second call which is for some mystery url.

(Note: calling cy.contains('SETTINGS').click() at the end of beforeEach somehow doesn't work, even in that first test. Even when I have my app set to start on the settings page (instead of having to click to it from inside the tests), clicking on SETTINGS from beforeEach still doesn't work even though we're already there. So that's kind of weird)

These full cypress logs may also be helpful:
image

@jrfornes
Copy link

I fixed the A mock server is already listening on this url issue with the following implementation:

// websocket-server-mock.ts
import { Server } from "mock-socket";

let mockServer = null;

export class WebsocketServerMock {
  constructor(url: string){
    if (mockServer) {
      mockServer.stop();
      mockServer.close();
      mockServer = null;
    }

    mockServer = new Server(url);
  }

  connect(callback){
    mockServer.on("connection", (socket: any) => {
      if ("function" === typeof callback) {
        callback(socket);
      }
    })
  }
}

And this is how I used this on a test.

import { WebSocket } from "mock-socket";
import { WebsocketServerMock } from "../../support/websocket-server-mock";

beforeEach(() => {
    cy.visit("/#/alarmcentral/global", {
        onBeforeLoad: (win) => {
          cy.stub(win, "WebSocket", (url) => new WebSocket(url));
        },
    });
})

...

it("should test something", () => {
    const { hostname, port } = window.location;
    const url = `ws://${hostname}:${port}/nxt-ui/app/websocket/spectralAnalysis`;
    const mockServer = new WebsocketServerMock(url);
    mockServer.connect((socket) => {
      socket.on("message", console.log);
      socket.send(`{"message":"Successfully connected!"}`);
    });
}

@Atrue
Copy link
Collaborator

Atrue commented Jul 25, 2022

@tuzmusic According to the screen you have 2 WebSockets called for the first test: /PM_Settings and /sockjs-node/..., so it's why only the latest ws is assigned to mockServer and is cleared in the afterEach hook. Looks like the second ws is related to the hot reloading so you don't need to catch it.
It's better to create the mock server first to avoid handling everything else in the stub callback:

mockServer = new Server(url)
mockServer.on('connection', socket => {
    console.log('mock socket connected');
    mockSocket = socket;
});
cy.stub(win, 'WebSocket', url => new WebSocket(url));

So here you will have only pre-defined servers and all other WebSockets will be rejected (It stops the hot reloading there but it doesn't affect the tests. But if you want to keep it you have to add some conditions for this URL in the stub callback to create a native WebSocket at this case)

@Atrue
Copy link
Collaborator

Atrue commented Jul 25, 2022

@jrfornes Looks like your mock server is running across multiple tests until the new instance of WebsocketServerMock is created. It's better to clear all mocks in afterEach hook, so it makes the code clear and avoids weird behavior. (Like If you forgot to create a new instance the previous mock server will catch the next WebSocket)

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

3 participants