Mocha: Mocha testing socketio middleware won't finish suite

Created on 6 Nov 2017  路  2Comments  路  Source: mochajs/mocha

Prerequisites

  • [x] Checked that your issue isn't already filed by cross referencing issues with the common mistake label
  • [x] Checked next-gen ES issues and syntax problems by using the same environment and/or transpiler configuration without Mocha to ensure it isn't just a feature that actually isn't supported in the environment in question or a bug in your code.
  • [x] 'Smoke tested' the code to be tested by running it outside the real test suite to get a better sense of whether the problem is in the code under test, your usage of Mocha, or Mocha itself
  • [x] Ensured that there is no discrepancy between the locally and globally installed versions of Mocha. You can find them with:
    node node_modules/.bin/mocha --version(Local) and mocha --version(Global). We recommend avoiding the use of globally installed Mocha.

Description

I am testing an nodejs-app with a server and a client component on nodejs 8.9.0 with mocha 4.0.1 and chai 4.1.2.

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.

Steps to Reproduce

copy into a minimal test-file:

// test.spec.js
'use strict'

const Express = require('express')
const Http = require('http')
const io = 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._socketio = io(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) => {
        const client = ioclient.connect(`http://localhost:${NODE_PORT}`)
        this._socketio.on('connection', () => {
            client.close()
            done()
        })
    })

    // 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) => {
        const client = ioclient.connect(`http://localhost:${NODE_PORT}`)

        this._socketio.use((socket, next) => {
            return next()
        })

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

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

Install dependencies: npm i [email protected] [email protected] [email protected] [email protected]

Run the Testfile: npx mocha test.spec.js

Expected behavior: [What you expect to happen]

All tests to run, and mocha exiting properly

Actual behavior: [What actually happens]

All tests run, mocha does not exit

Reproduces how often: [What percentage of the time does it reproduce?]

100%

Versions

see above

Most helpful comment

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()
    })
})

All 2 comments

Note: This is a cross-post from https://stackoverflow.com/q/47094615/898999

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()
    })
})
Was this page helpful?
0 / 5 - 0 ratings