In OP's code,
socket.on('connect', function(done) {
console.log('worked...');
done();
});
the done
was applied to the wrong callback. It should be removed from the socket.on
callback and added to Mocha's it
block callback:
it('First (hopefully useful) test', function (done) {
var socket = io.connect('http://localhost:3001');
socket.on('connect', function () {
console.log('worked...');
done();
});
});
A complete example
Existing answers are great but don't show the server ultimately being tested. Here's a complete version with console.log
s to illustrate what's going on. Explanation follows.
src/server.js
:
const express = require("express");
const createServer = (port=3000) => {
const app = express();
const http = require("http").Server(app);
const io = require("socket.io")(http);
io.on("connection", socket => {
console.log("[server] user connected");
socket.on("message", msg => {
console.log(`[server] received '${msg}'`);
socket.emit("message", msg);
});
socket.on("disconnect", () => {
console.log("[server] user disconnected");
});
});
http.listen(port, () =>
console.log(`[server] listening on port ${port}`)
);
return {
close: () => http.close(() =>
console.log("[server] closed")
)
};
};
module.exports = {createServer};
test/server.test.js
:
const {expect} = require("chai");
const io = require("socket.io-client");
const {createServer} = require("../src/server");
const socketUrl = "http://localhost:3000";
describe("server", function () {
this.timeout(3000);
let server;
let sockets;
beforeEach(() => {
sockets = [];
server = createServer();
});
afterEach(() => {
sockets.forEach(e => e.disconnect())
server.close();
});
const makeSocket = (id=0) => {
const socket = io.connect(socketUrl, {
"reconnection delay": 0,
"reopen delay": 0,
"force new connection": true,
transports: ["websocket"],
});
socket.on("connect", () => {
console.log(`[client ${id}] connected`);
});
socket.on("disconnect", () => {
console.log(`[client ${id}] disconnected`);
});
sockets.push(socket);
return socket;
};
it("should echo a message to a client", done => {
const socket = makeSocket();
socket.emit("message", "hello world");
socket.on("message", msg => {
console.log(`[client] received '${msg}'`);
expect(msg).to.equal("hello world");
done();
});
});
it("should echo messages to multiple clients", () => {
const sockets = [...Array(5)].map((_, i) => makeSocket(i));
return Promise.all(sockets.map((socket, id) =>
new Promise((resolve, reject) => {
const msgs = [..."abcd"].map(e => e + id);
msgs.slice().forEach(e => socket.emit("message", e));
socket.on("message", msg => {
console.log(`[client ${id}] received '${msg}'`);
expect(msg).to.equal(msgs.shift());
if (msgs.length === 0) {
resolve();
}
});
})
));
});
});
In summary, the server exports a function that lets a server app be created from scratch, allowing each it
block to be idempotent and avoid server state from carrying between tests (assuming no persistence on the server otherwise). Creating an app returns an object with a close
function. socket.disconnect()
must be called per socket in each test to avoid timeouts.
Given these requirements, the testing suite follows this per-test setup/teardown workflow:
let server;
let sockets;
beforeEach(() => {
sockets = [];
server = createServer();
});
afterEach(() => {
sockets.forEach(e => e.disconnect())
server.close();
});
makeSocket
is an optional helper to reduce the repeated boilerplate of connecting and disconnecting a socket client. It does produce a side effect on the sockets
array for cleanup later, but this is an implementation detail from the it
block's perspective. Test blocks shoudn't touch server
or sockets
variables, although other workflows are likely depending on need. The critical takeaways are test case idempotency and closing all connections after each test case.
Options on the socket.connect
object on the client let you choose transport and behavior of the socket. "force new connection": true
creates a new Manager
per socket instead of reusing an existing one and transports: ["websocket"]
upgrades to WS protocol from long polling immediately.
Use it("should ... ", done => { /* tests */ });
and invoke done()
after all work is completed in callbacks or return a promise (and omit the done
parameter to the it
callback). The example above shows both approaches.
Used in this post:
node
: 12.19.0
chai
: 4.2.0
express
: 4.16.4
mocha
: 5.2.0
socket.io
: 2.2.0
socket.io-client
: 2.2.0