How do I fix: "TypeError: wsModule.Server is not a constructor" when running tests in Jest

Viewed 5170

I'm new to Jest and I want to start writing some integration tests for a Node.js server. When I try and run a test, I receive a "TypeError: wsModule.Server is not a constructor" error. Why won't my test environment initialise the socket server?

server.js:

const app = require('express')();
const server = require('http').Server(app);
const io = require('socket.io')(server); <-- TEST FAILS BECAUSE OF SOCKET MODULE
const router = require('./router/router');
const bodyParser = require('body-parser');
const cors = require('cors');
require('./socket/socket')(io); 

// Allow CORS so our client can consume JSON
app.use(cors())

// Takes the raw requests and turns them into usable properties on req.body
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

// Use our router
app.use('/', router);

server.listen(3000, (req, res) => {
  console.log("listen at 3000!");
});

module.exports = app;

socket.js:

const userService = require('../services/userService');
const roomService = require('../services/roomService');

module.exports = function (io) {

  io.on("connection", socket => {

    console.log('there has been a connection with: ' + socket.id);

    socket.on('set-username', ({ roomId, username }) => {
      userService.setUsername(roomId, username, socket, io);
    });

    socket.on('start-game', ({ roomId, hostName }) => {
      roomService.startGame(roomId, hostName, socket, io);
    });

});

roomService.test.js:

const chai = require('chai');
const chaiHttp = require('chai-http');
const server = require('../../server');
const { expect } = chai;
chai.use(chaiHttp);

describe("example", () => {
  test('should run', () => {
    expect(true).to.be.true;
  })
})
1 Answers

The test fails because server.listen is asynchronous and your tests is synchronous

Why won't my test environment initialize the socket server?

The problem is that your tests will complete before the HTTP server is started and the Socket.IO server is attached to it.

see Testing Asynchronous Code

You can comment server.listen in server.js and export the server so you can use it while testing and tell jest to wait until the server is started then run the rest of the tests.

server.js

- server.listen(3000, (req, res) => { console.log("listen at 3000!"); });
- module.exports = app;

+ module.exports = server

roomService.test.js

const server = require('../../server');

describe("example", () => {
    beforeAll(done => { //pass a callback to tell jest it is async
        //start the server before any test
        server.listen(3000, () => done());
    })

    afterAll(done => { //pass a callback to tell jest it is async
        //close the server after all tests
        server.listening ? server.close(() => done()) : done();
    })

    test('should run', () => {
        expect(true).to.be.true;
    })
})

Of course you must uncomment it when you want to run your app again in developpment mode.

To avoid commenting/uncommenting in this file create a separate module where you start your server and make it the app entry point.

Here is how your files would look like

server.js

const app = require('./app');
const server = require('http').Server(app);
const io = require('socket.io')(server);
require('./socket/socket')(io); 
module.exports = server;

start.js:

const server = require('./server');
server.listen(3000, (req, res) => {
    console.log("listen at 3000!");
});

app.js

const app = require('express')();
const router = require('./router/router');
const bodyParser = require('body-parser');
const cors = require('cors');

app.use(cors())
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use('/', router);

module.exports = app;

With start.js the app entry point, you can now run the app and the tests without making any changes

Related