Search code examples
node.jstestingwebsocketmocha.jsws

mocha test not exiting when using websockets, even with websocket.close/termintae


I'm trying to test my websocket server, by opening a websocket client in my mocha test file, connection to the ws server and awaiting response. I send an http request to the ws server, and then a message is sent via websocket to the client, where I store the results and test it.

I get the response I want and the test passes, but the mocha test itself does not terminate and I'm forced to close it manually.

I have read this - explaining that there is probably still some async process open, such as an open socket, but I try to terminate the socket, and I get the 'close' event to fire (I get the console log message I defined in the 'close' event listener), but the test still isn't over.

I'm using ws (npm), mocha, chai (for asserts) and supertest (to invoke the server to send a response).

versions:

"ws": "^7.3.0",

"mocha": "^7.0.0",

"chai": "^4.2.0",

"supertest": "^4.0.2",

node: v12.9.1

I know I can use the --exit flag, as is suggested in this stack overflow answer, but I prefer not to, if it can be avoided.

Here is the relevant code:

'use strict';
const supertest = require('supertest');
const assert = require('chai').assert;
const paths = require('../common/paths');
const { sign } = require('../common/signing');
const WebSocket = require('ws');

describe.only('Events server tests', function () {
    this.timeout(11000);

    const docId = 'doc_events_' + Date.now();
    const wsServerUrl = 'ws://localhost:8080/subscribe?params={"prefix_doc_id":"doc_events_"}';
    const ws = new WebSocket(wsServerUrl);
    let id_from_msg;
    // Connection opened
    ws.addEventListener('open', function (event) {
        console.log('connection started!');
    });
    // Listen for messages
    ws.addEventListener('message', function (event) {
        console.log('\n\nMessage from server ', event.data, ' ', typeof event.data);
        try {
            // the msg recived via websocket is in the form of: "doc_id":X, and I store the docID in order to check if it matches the docId that was sent in the test.
            if (event.data.includes('doc_id')) {
                id_from_msg = JSON.parse(event.data).doc_id;
            }
        } catch (error) {
            console.log('error: ', error);
        }
    });
    ws.addEventListener('close', () => {
         console.log('closed connection!');
    });

    before((done) => {
        console.log('start');
        done();
    });

    after((done) => {

        ws.terminate();
        // ws.close();
        done();
        console.log('after?');
    });

    it('Test 1 - send simple request to events server', (done) => {
        const eventsUrl = paths.EVENTS.EVENTS();
        const eventsObj = {
            reqId: '',
            docId: docId,
            sessionId: 1,
            status: 200
        };
        // This tests is used to invoke response from the socket server, and it works fine, the tests passes and ends without an issue.
        supertest('http://localhost:3035')
            .post(eventsUrl)
            .set('Authorization', sign('post', eventsUrl, eventsObj))
            .send(eventsObj)
            .expect(200)
            .expect(res => {
                assert.ok(res.body);
                assert.equal(id_from_msg, docId);
            })
            .end(done);
    });

});

As you can see, I tried both ws.close() and ws.terminate() inside the "after" section, and both yield the same result: the test does not end, and the console.log('after?') line is fired after done() is called.

I tried to overwrite 'onclose' method and to fire in manually, but to no avail.

I tried also to close the websocket in the test itself (I mean in the 'it' section, even though I don't like it semantically) - but the test suit itself does not terminate, same as before.

Is there a way to make sure that the websocket is properly closed before done() is called in after?


Solution

  • I found a solution, and since its very weird, I post it here, so if anyone encounter something similar he/she can find help:

    The core of the issue is that some async process is still open when the test is suppose to end, but it seems the in this test all the websockets are closed.

    But here is the weird part - in this test it's true that all the ws are closed, in other test its not.

    It found that I had another test with the same basic structure - in the "describe" section I had:

    const docId = 'doc_events_' + Date.now();
    const wsServerUrl = 'ws://localhost:8080/subscribe?params={"prefix_doc_id":"doc_events_"}';
    const ws = new WebSocket(wsServerUrl);
    

    And even if I had ".only" on the test I wanted to run, It seems that mocha runs all describes from all tests, event if there is an "only" flag on one of them.

    Since the "describe" section of the other test was run, there was another open websocket, so the test was stuck.

    That is a very weird behavior, and maybe I will contact the mocha team in the future about it, but for now - I hope this can help.