Search code examples
node.jssocket.iomocha.jsmiddleware

Mocha testing socketio middleware won't finish suite


I am testing an nodejs-app with a server and a client component on nodejs 8.9 with mocha.

For mocha to end properly, I have to make sure that all socketio and http-servers are closed after the tests have been run. This works fine with normal tests, but as soon as I register a middleware to the socketio-server, the mocha-process won't close and stay open forever.

Testcode (comment in the second test to see the problem, run via mocha test.spec.js):

// test.spec.js
'use strict'

const Express = require('express')
const Http = require('http')
const ioserver = require('socket.io')
const ioclient = require('socket.io-client')

const NODE_PORT = process.env.NODE_PORT || 3000

describe('Client', function () {
    beforeEach(() => {
        const express = new Express()
        this._http = Http.Server(express)
        this._ioserver = ioserver(this._http)
        this._http.listen(NODE_PORT)
    })

    // this test works perfectly, even when I copy it and run it
    // multiple times in this suite
    it('should connect to a socketio-server', (done) => {
        this._ioserver.on('connection', () => {
            client.close()
            done()
        })
        const client = ioclient.connect(`http://localhost:${NODE_PORT}`)
    })

    // this test also finished, but the suite hangs afterwards - as if
    // a socket-client or socket-server was not closed properly.
    it('should finish the test suite even with a middleware', (done) => {
        this._ioserver.use((socket, next) => {
            return next()
        })

        this._ioserver.on('connection', () => {
            client.close()
            done()
        })

        const client = ioclient.connect(`http://localhost:${NODE_PORT}`)
    })

    afterEach(() => {
        this._ioserver.close()
        this._http.close()
    })
})

Any ideas why that happens?


Solution

  • So, the problem was, that the server closed the client connection on a successful connection event. The client did not get any information on that, but instead saw a failed connection and tried to reconnect. This opened a socket to the server again and because the server was already closed, the connection error kept coming.

    This behavior stopped node from properly destroying all objects, which in turn explaines the hanging. The solution is to call done() only after the client has declared a connection open, not after the server has declared a connection open like so:

    'use strict'
    
    const Express = require('express')
    const Http = require('http')
    const ioserver = require('socket.io')
    const ioclient = require('socket.io-client')
    
    const NODE_PORT = process.env.NODE_PORT || 3000
    
    describe('Client', function () {
        beforeEach(() => {
            const express = new Express()
            this._http = Http.Server(express)
            this._ioserver = ioserver(this._http)
            this._http.listen(NODE_PORT)
            this._client = null
        })
    
        it('should connect to a socketio-server', (done) => {
            this._ioserver.on('connection', () => {
                done()
            })
    
            this._client = ioclient.connect(`http://localhost:${NODE_PORT}`)
        })
    
        it('should finish the test suite even with a middleware', (done) => {
            this._ioserver.use((socket, next) => {
                return next()
            })
    
            this._client = ioclient.connect(`http://localhost:${NODE_PORT}`)
    
            // if we wait for the server and kill the server socket,
            // the client will try to reconnect and won't be killed
            // by mocha.
            this._client.on('connect', () => {
                done()
            })
        })
    
        afterEach(() => {
            // this last call forces the client to stop connecting
            // even if tests failed
            this._client.close()
    
            this._ioserver.close()
            this._http.close()
        })
    })