Call a function everytime the client is idle - node.js

First time trying TCP and made a program which returns the square of the number sent by the client.
How to ask the client for a number everytime they are idle for 'n' seconds?
I tried the setTimeout method but it triggers after those 'n' seconds have passed and then it does does not get triggered again.
Client:
const net = require('net');
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
const options = {
port : 1234
};
const client = net.createConnection(options, () => {
console.log("Connected to server")
});
client.on('data', (data) => {
console.log(data.toString());
});
client.setTimeout(2000, () => {
readline.question('Number to be squared: ',(num) => {
client.write(num);
});
});
Server:
const net = require('net');
const port = 1234;
const server = net.createServer(conn => {
console.log('New client joined');
conn.on('data', (data) => {
console.log(`Data received from client: ${data}`)
data = parseInt(data);
data = Math.pow(data,2);
conn.write('From server- '+data.toString());
});
conn.on('end',() => {
console.log('Connection stopped');
});
conn.on('error',(e) => {
console.log('Connection stopped-', e.message);
});
});
server.listen(port);

You need to listen to the timeout event, callback will be called only once. From the doc:
The optional callback parameter will be added as a one-time listener for the 'timeout' event.
socket.setTimeout(3000);//setting here.
socket.on('timeout', () => {
console.log('socket timeout');
socket.end();
});

Related

Socket not emit messages

I've had a problem with receiving message from client to server with using React and ExpressJS. After launch sendMessage function on client side I want to send to server my message, but, I don't know why this message is not being received by my server and io.on("message", (message) => { is not launched with his console.log :/
Here is my code
Server side:
index.ts
const server = http.createServer(app);
export const socketIo = socket(server);
socket.ts
export const socket = (httpServer: any) => {
const io = new Server(httpServer, { cors: { origin: "http://localhost:5000" } });
io.on("connection", (socket) => {
console.log("Socket connected!")
socket.emit('connection', null);
});
io.on("message", (message) => {
console.log("NEW MESSAGE: ", message)
})
}
Client side:
App.tsx
const SOCKET_SERVER = "http://127.0.0.1:3000";
export const socket = socketClient(SOCKET_SERVER);
socket.on('connect', () => {
console.log('CONNECTED WITH BACKEND SOCKET')
})
Chat.tsx
const sendMessage = () => {
const message = form.getFieldsValue()["typedMessage"];
socket.emit("message", message);
form.resetFields();
};
thanks for any help!
io.on("connection", (socket) => {
console.log("Socket connected!")
socket.emit('connection', null);
});
io.on("message", (message) => {
console.log("NEW MESSAGE: ", message)
})
The "connection" is triggered on the server listener and provides a connected socket. The socket is then used to receive messages. This means receiving messages must be done on socket, not on the listener io:
io.on("connection", (socket) => {
console.log("Socket connected!")
socket.emit('connection', null);
socket.on("message", (message) => {
console.log("NEW MESSAGE: ", message)
});
});

NodeJS server socket - read client msg server.on('data')

I found these two links about clinet/server socket programming in NodeJS:
TCP Example &
NetJS;
So i configured my micro server like this:
const SOCKETServer = net.createServer((socket) => {
socket.write('Server is listening!\r\n');
socket.pipe(socket);
});
SOCKETServer.on('data', (data) => {
console.log('CLIENT: ' + data.toString());
});
SOCKETServer.listen(5000, '127.0.0.1');
and also my client like this:
const net = require('net');
const client = new net.Socket();
client.connect(5000, '127.0.0.1', () => {
setTimeout(sender, 1500, null);
});
client.on('data', (data) => {
console.log('SERVER: ' + data);
});
let sender = () => {
client.write('Hello Server\r\n');
setTimeout(sender, 1500, null);
};
The code works fine and i can get data from server in client. I also can send data to server from client and i can read and see its feedback in client cause of socket.pipe(socket). The problem is i cannot read anything in data event in server.
Here:
SOCKETServer.on('data', (data) => {
console.log('CLIENT: ' + data.toString());
});
net.Server doesn't have data event.
You should read from incoming socket in createServer
const SOCKETServer = net.createServer((socket) => {
socket.write('Server is listening!\r\n');
socket.on('data', function(data) {
console.log('CLIENT:', data.toString());
})
socket.pipe(socket);
});

how can i emit data to other client (user) using socket.io?

I am sending data to all clients but it only APPEND on sender's Message body. In this case, real-time data is only working on sender only but i need to work on every connected users.
After reading the documentation it says, BROADCASTING could be the solution but its not showing for sender(Which means OK) But that also not showing for other connected receivers.
Custom.js
var socket = io.connect("http://localhost:3000/");
$.ajax({
url: 'sent',
type: 'POST',
data: {
msg: 'Some message'
},
dataType: "json",
success: function (data) {
if (data.message) {
socket.emit('send', {
msg: data.msgResult
});
socket.on('msgResult', result => {
$(".msgDiv").append(result);
});
}
}
});
App.js
const app = express();
const http = require("http").Server(app);
const io = require("socket.io")(http);
io.on('connection', (socket) => {
console.log('Socket.io connected...');
socket.on('send', (data) => {
socket.emit('msgResult', data.msg);
});
socket.on('disconnect', () => {
console.log("A socket Discounted ..");
});
});
I want to append data to all connected users including sender too.
If you want to send message to all connected sockets you can use
io.sockets.emit('msgResult', 'data');
and if you want to send message to all connected sockets except sender, use
socket.broadcast.emit('msgResult', 'data');
your index.js for socket server should have
//webServerPort= localhost:3000
const server = http.createServer(app);
let constAppServer = server.listen(webServerPort);
let io = socketServer(constAppServer);
app.set('socket',io);
io.on('connection', function (socket) {
console.log('connection opened');
socket.on('disconnect', function(){
console.log('user disconnected');
});
socket.on('udagent',function(msg){
console.log('message: ' + msg);
});
});
this is your event.js when you want to send a event to frontend
const testFunction =(req,res)=> {
let io = req.app.get('socket');
io.emit('dashboard_event', { "totalMin": data });
}
i had api for broadcasting my admin notifications to all the agents under me by creating and passing this api
const broadCastUpdates =(req,res)=> {
const {message} = req.body
let io = req.app.get('socket');
io.broadcast.emit('broadCastToAgents', { 'data':message });
}
Finally i found my answer. it was a simple mistake which takes a lot of time.
custom.js
var socket = io.connect("http://localhost:3000/");
$.ajax({
url: 'sent',
type: 'POST',
data: {
msg: 'Some message'
},
dataType: "json",
success: function (data) {
if (data.message) {
socket.emit('send', {
msg: data.msgResult
});
}
}
});
socket.on('msgResult', result => {
$(".msgDiv").append(result);
});
App.js
const app = express();
const http = require("http").Server(app);
const io = require("socket.io")(http);
io.on('connection', (socket) => {
console.log('Socket.io connected...');
socket.on('send', (data) => {
socket.emit('msgResult', data.msg);
});
socket.on('disconnect', () => {
console.log("A socket Discounted ..");
});
});
i just plug out my msgResult from ajax submission. That's it.

Reconnect net.socket nodejs

I am new to node.js and would like to connect to a TCP socket. For this I am using the net module.
My idea was to wrap the connect sequence into a function then on the 'close' event, attempt a reconnection. Not that easy apparently.
function conn() {
client.connect(HOST_PORT, HOST_IP, function() {
startSequence();
})
}
client.on('close', function(e) {
log('info','Connection closed! -> ' + e)
client.destroy();
setTimeout(conn(),1000);
});
So when the remote host is closed, I see my logs comming through, howere what seems to be happening is that as soons as the remote host comes online ALL the previous attempts start to get processed - if that makes sense. If you look at client.connect, there is a function called startSequence that sends some data that "iniates" the connection from the remote server side. When the server goes offline and I start reconnecting all the failed attempts from before seem to have been buffered and are all sent together when the server goes online.
I have tried the code from this Stackoverflow link as well to no avail (Nodejs - getting client socket to try again after 5 sec time out)
client.connect(HOST_PORT, HOST_IP, function() {
pmsStartSequence();
})
// Add a 'close' event handler for the client socket
client.on('close', function(e) {
log('debug','connection closed -> ' + e)
client.setTimeout(10000, function() {
log('debug', 'trying to reconnect')
client.connect(HOST_PORT, HOST_IP, function() {
pmsStartSequence();
})
})
});
Is there any advice on how I can reconnect a socket after failure?
Inspired from the other solutions, I wrote this, it's tested, it works !
It will keep on trying every 5 sec, until connection is made, works if it looses connection too.
/* Client connection */
/* --------------------------------------------------------------------------------- */
const client = new net.Socket()
var intervalConnect = false;
function connect() {
client.connect({
port: 1338,
host: '127.0.0.1'
})
}
function launchIntervalConnect() {
if(false != intervalConnect) return
intervalConnect = setInterval(connect, 5000)
}
function clearIntervalConnect() {
if(false == intervalConnect) return
clearInterval(intervalConnect)
intervalConnect = false
}
client.on('connect', () => {
clearIntervalConnect()
logger('connected to server', 'TCP')
client.write('CLIENT connected');
})
client.on('error', (err) => {
logger(err.code, 'TCP ERROR')
launchIntervalConnect()
})
client.on('close', launchIntervalConnect)
client.on('end', launchIntervalConnect)
connect()
The problem is where you set the on-connect callback.
The doc of socket.connect() says:
connectListener ... will be added as a listener for the 'connect' event once.
By setting it in socket.connect() calls, every time you try reconnecting, one more listener (a one-time one), which calls startSequence(), is attached to that socket. Those listeners will not be fired until reconnection successes, so you got all of them triggered at the same time on a single connect.
One possible solution is separating the connect listener from socket.connect() calls.
client.on('connect', function() {
pmsStartSequence();
});
client.on('close', function(e) {
client.setTimeout(10000, function() {
client.connect(HOST_PORT, HOST_IP);
})
});
client.connect(HOST_PORT, HOST_IP);
My solution:
var parentHOST = '192.168.2.66';
var parentPORT = 9735;
var net = require('net');
var S = require('string');
var parentClient = new net.Socket();
var parentActive = false;
var startParentClient = function () {
parentClient = new net.Socket();
parentActive = false;
parentClient.connect(parentPORT, parentHOST, function() {
console.log('Connected ' + cluster.worker.id + ' to parent server: ' + parentHOST + ':' + parentPORT);
parentActive = true;
});
parentClient.on('error', function() {
parentActive = false;
console.log('Parent connection error');
});
parentClient.on('close', function() {
parentActive = false;
console.log('parent connection closed');
setTimeout(startParentClient(), 4000);
});
}
If is necessary connect:
if (!S(parentHOST).isEmpty() && !S(parentPORT).isEmpty()) {
startParentClient();
}
As mentioned multiple times in the comments, you need to use .removeAllListeners() before trying to reconnect your client to the server in order to avoid having multiple listeners on the same event.
The code below should do the trick
Note that I try to reconnect the client after the close and end events because these two events can be fired in different orders after closing a connection
const net = require("net")
let client = new net.Socket()
function connect() {
console.log("new client")
client.connect(
1337,
"127.0.0.1",
() => {
console.log("Connected")
client.write("Hello, server! Love, Client.")
}
)
client.on("data", data => {
console.log("Received: " + data)
})
client.on("close", () => {
console.log("Connection closed")
reconnect()
})
client.on("end", () => {
console.log("Connection ended")
reconnect()
})
client.on("error", console.error)
}
// function that reconnect the client to the server
reconnect = () => {
setTimeout(() => {
client.removeAllListeners() // the important line that enables you to reopen a connection
connect()
}, 1000)
}
connect()
I use the following code to achieve reconnection with node.js. I am not a Javascript expert so I guess it can be improved but it nevertheless works fine for me.
I hope this could help.
Best.
//----------------------------------------------------------------//
// SocketClient //
//----------------------------------------------------------------//
var net = require('net');
var SocketClient = function(host, port, data_handler, attempt)
{
var node_client;
var attempt_index = (attempt ? attempt : 1);
this.m_node_client = new net.Socket();
node_client = this.m_node_client;
this.m_node_client.on('close', function()
{
var new_wrapper = new SocketClient(host, port, data_handler, attempt_index + 1);
node_client.destroy();
new_wrapper.start();
});
this.m_node_client.on('data', data_handler);
this.m_node_client.on('error', function(data)
{
console.log("Error");
});
this.start = function()
{
this.m_node_client.connect(port, host, function()
{
console.log('Connected ' + attempt_index);
});
};
};
//----------------------------------------------------------------//
// Test //
//----------------------------------------------------------------//
var test_handler = function(data)
{
console.log('TestHandler[' + data + ']');
};
var wrapper = new SocketClient('127.0.0.1', 4000, test_handler);
wrapper.start();
I have tried re-using the same socket connection, by using this:
const s = net.createConnection({port});
s.once('end', () => {
s.connect({port}, () => {
});
});
that didn't work, from the server-side's perspective. If the client connection closes, it seems like a best practice to create a new connection:
const s = net.createConnection({port});
s.once('end', () => {
// create a new connection here
s = net.createConnection(...);
});
sad but true lulz.
Following this:
//
// Simple example of using net.Socket but here we capture the
// right events and attempt to re-establish the connection when
// is is closed either because of an error establishing a
// connection or when the server closes the connection.
//
// Requires
const net = require('net');
// Create socket
const port = 5555;
const host = '127.0.0.1';
const timeout = 1000;
let retrying = false;
// Functions to handle socket events
function makeConnection () {
socket.connect(port, host);
}
function connectEventHandler() {
console.log('connected');
retrying = false;
}
function dataEventHandler() {
console.log('data');
}
function endEventHandler() {
// console.log('end');
}
function timeoutEventHandler() {
// console.log('timeout');
}
function drainEventHandler() {
// console.log('drain');
}
function errorEventHandler() {
// console.log('error');
}
function closeEventHandler () {
// console.log('close');
if (!retrying) {
retrying = true;
console.log('Reconnecting...');
}
setTimeout(makeConnection, timeout);
}
// Create socket and bind callbacks
let socket = new net.Socket();
socket.on('connect', connectEventHandler);
socket.on('data', dataEventHandler);
socket.on('end', endEventHandler);
socket.on('timeout', timeoutEventHandler);
socket.on('drain', drainEventHandler);
socket.on('error', errorEventHandler);
socket.on('close', closeEventHandler);
// Connect
console.log('Connecting to ' + host + ':' + port + '...');
makeConnection();
function createServer() {
const client = new net.Socket();
client.connect(HOST_PORT, HOST_IP, function() {
console.log("Connected");
state = 1 - state;
client.write(state.toString());
});
client.on("data", function(data) {
console.log("Received: " + data);
//client.destroy(); // kill client after server's response
});
client.on("close", function() {
console.log("Connection closed");
//client.connect()
setTimeout(createServer, 2000);
});
}
createServer();

Unit testing Node.js and WebSockets (Socket.io)

Could anyone provide a rock-solid, dead-simple unit test for Node.js using WebSockets (Socket.io)?
I'm using socket.io for Node.js, and have looked at socket.io-client for establishing the client connection to a server in the test. However, I seem to be missing something.
In the example below, "worked..." never gets printed out.
var io = require('socket.io-client')
, assert = require('assert')
, expect = require('expect.js');
describe('Suite of unit tests', function() {
describe('First (hopefully useful) test', function() {
var socket = io.connect('http://localhost:3001');
socket.on('connect', function(done) {
console.log('worked...');
done();
});
it('Doing some things with indexOf()', function() {
expect([1, 2, 3].indexOf(5)).to.be.equal(-1);
expect([1, 2, 3].indexOf(0)).to.be.equal(-1);
});
});
});
Instead, I simply get:
Suite of unit tests
First (hopefully useful) test
✓ Doing some things with indexOf()
1 test complete (26 ms)
Any suggestions?
After further poking and prodding, I found some incredibly useful information. In the author's example, he points out the critical step of establishing socket listeners in the before hooks.
This example works:
Assuming a server is listening for socket connections at localhost:3001, of course
var io = require('socket.io-client')
, assert = require('assert')
, expect = require('expect.js');
describe('Suite of unit tests', function() {
var socket;
beforeEach(function(done) {
// Setup
socket = io.connect('http://localhost:3001', {
'reconnection delay' : 0
, 'reopen delay' : 0
, 'force new connection' : true
});
socket.on('connect', function() {
console.log('worked...');
done();
});
socket.on('disconnect', function() {
console.log('disconnected...');
})
});
afterEach(function(done) {
// Cleanup
if(socket.connected) {
console.log('disconnecting...');
socket.disconnect();
} else {
// There will not be a connection unless you have done() in beforeEach, socket.on('connect'...)
console.log('no connection to break...');
}
done();
});
describe('First (hopefully useful) test', function() {
it('Doing some things with indexOf()', function(done) {
expect([1, 2, 3].indexOf(5)).to.be.equal(-1);
expect([1, 2, 3].indexOf(0)).to.be.equal(-1);
done();
});
it('Doing something else with indexOf()', function(done) {
expect([1, 2, 3].indexOf(5)).to.be.equal(-1);
expect([1, 2, 3].indexOf(0)).to.be.equal(-1);
done();
});
});
});
I found that the placement of done() in the beforeEach, socket.on('connect'...) listener was crucial to having the connection get established. For example, if you comment out done() in the listener, then add it one scope out (just before exiting the beforeEach), you'll see the "no connection to break..." message instead of the "disconnecting..." message. Like so:
beforeEach(function(done) {
// Setup
socket = io.connect('http://localhost:3001', {
'reconnection delay' : 0
, 'reopen delay' : 0
, 'force new connection' : true
});
socket.on('connect', function() {
console.log('worked...');
//done();
});
socket.on('disconnect', function() {
console.log('disconnected...');
});
done();
});
I'm new to Mocha, so there's probably a very obvious reason to the initiated for placing done() within the socket scope itself. Hopefully that little detail will save others in my shoes from hair pulling.
For me, the above test (with correct scoping of done()) outputs:
Suite of unit tests
First (hopefully useful) test
◦ Doing some things with indexOf(): worked...
✓ Doing some things with indexOf()
disconnecting...
disconnected...
◦ Doing something else with indexOf(): worked...
✓ Doing something else with indexOf()
disconnecting...
disconnected...
2 tests complete (93 ms)
Offering an extension of the accepted answer here. Has basic client to server communication useful as boilerplate for other future tests. Using mocha, chai, and expect.
var io = require('socket.io-client')
, io_server = require('socket.io').listen(3001);
describe('basic socket.io example', function() {
var socket;
beforeEach(function(done) {
// Setup
socket = io.connect('http://localhost:3001', {
'reconnection delay' : 0
, 'reopen delay' : 0
, 'force new connection' : true
, transports: ['websocket']
});
socket.on('connect', () => {
done();
});
socket.on('disconnect', () => {
// console.log('disconnected...');
});
});
afterEach((done) => {
// Cleanup
if(socket.connected) {
socket.disconnect();
}
io_server.close();
done();
});
it('should communicate', (done) => {
// once connected, emit Hello World
io_server.emit('echo', 'Hello World');
socket.once('echo', (message) => {
// Check that the message matches
expect(message).to.equal('Hello World');
done();
});
io_server.on('connection', (socket) => {
expect(socket).to.not.be.null;
});
});
});
Dealing with callbacks and promises yourself can be difficult and non trivial examples quickly become very complex and hard to read.
There is a tool called socket.io-await-test available via NPM that allows you to suspend/wait in a test until events have been triggered using the await keyword.
describe("wait for tests", () => {
it("resolves when a number of events are received", async () => {
const tester = new SocketTester(client);
const pongs = tester.on('pong');
client.emit('ping', 1);
client.emit('ping', 2);
await pongs.waitForEvents(2) // Blocks until the server emits "pong" twice.
assert.equal(pongs.get(0), 2)
assert.equal(pongs.get(1), 3)
})
})
Check out this boilerplate solution that's based on promises and good practice.
You can test your servers entire io events with it, no sweat.
You just need to copy a boilerplate test and add your own code as needed.
Checkout the repo on GitHub for full source code.
https://github.com/PatMan10/testing_socketIO_server
const io = require("socket.io-client");
const ev = require("../utils/events");
const logger = require("../utils/logger");
// initSocket returns a promise
// success: resolve a new socket object
// fail: reject a error
const initSocket = () => {
return new Promise((resolve, reject) => {
// create socket for communication
const socket = io("localhost:5000", {
"reconnection delay": 0,
"reopen delay": 0,
"force new connection": true
});
// define event handler for sucessfull connection
socket.on(ev.CONNECT, () => {
logger.info("connected");
resolve(socket);
});
// if connection takes longer than 5 seconds throw error
setTimeout(() => {
reject(new Error("Failed to connect wihtin 5 seconds."));
}, 5000);
}
);
};
// destroySocket returns a promise
// success: resolve true
// fail: resolve false
const destroySocket = socket => {
return new Promise((resolve, reject) => {
// check if socket connected
if (socket.connected) {
// disconnect socket
logger.info("disconnecting...");
socket.disconnect();
resolve(true);
} else {
// not connected
logger.info("no connection to break...");
resolve(false);
}
});
};
describe("test suit: Echo & Bello", () => {
test("test: ECHO", async () => {
// create socket for communication
const socketClient = await initSocket();
// create new promise for server response
const serverResponse = new Promise((resolve, reject) => {
// define a handler for the test event
socketClient.on(ev.res_ECHO, data4Client => {
//process data received from server
const { message } = data4Client;
logger.info("Server says: " + message);
// destroy socket after server responds
destroySocket(socketClient);
// return data for testing
resolve(data4Client);
});
// if response takes longer than 5 seconds throw error
setTimeout(() => {
reject(new Error("Failed to get reponse, connection timed out..."));
}, 5000);
});
// define data 4 server
const data4Server = { message: "CLIENT ECHO" };
// emit event with data to server
logger.info("Emitting ECHO event");
socketClient.emit(ev.com_ECHO, data4Server);
// wait for server to respond
const { status, message } = await serverResponse;
// check the response data
expect(status).toBe(200);
expect(message).toBe("SERVER ECHO");
});
test("test BELLO", async () => {
const socketClient = await initSocket();
const serverResponse = new Promise((resolve, reject) => {
socketClient.on(ev.res_BELLO, data4Client => {
const { message } = data4Client;
logger.info("Server says: " + message);
destroySocket(socketClient);
resolve(data4Client);
});
setTimeout(() => {
reject(new Error("Failed to get reponse, connection timed out..."));
}, 5000);
});
const data4Server = { message: "CLIENT BELLO" };
logger.info("Emitting BELLO event");
socketClient.emit(ev.com_BELLO, data4Server);
const { status, message } = await serverResponse;
expect(status).toBe(200);
expect(message).toBe("SERVER BELLO");
});
});
---- Foot Note ----
Depending on how you setup your server environment, you may experience environmental conflict between socket.io and socket.io-client running from the same project simultaneously. In which case it would be better to separate the project into a "test client" and a server. Checkout below repo if you get this issue.
https://github.com/PatMan10/testing_socketIO_server_v2
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.logs 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
I had this problem: How to do unit test with a "socket.io-client" if you don't know how long the server take to respond?.
I've solved so using mocha and chai:
var os = require('os');
var should = require("chai").should();
var socketio_client = require('socket.io-client');
var end_point = 'http://' + os.hostname() + ':8081';
var opts = {forceNew: true};
describe("async test with socket.io", function () {
this.timeout(10000);
it('Response should be an object', function (done) {
setTimeout(function () {
var socket_client = socketio_client(end_point, opts);
socket_client.emit('event', 'ABCDEF');
socket_client.on('event response', function (data) {
data.should.be.an('object');
socket_client.disconnect();
done();
});
socket_client.on('event response error', function (data) {
console.error(data);
socket_client.disconnect();
done();
});
}, 4000);
});
});

Resources