Jest test WebSocket client API - node.js

Context :
I have a WebSocket server program (.exe build with C#) and i want to test my request with a NodeJs client (in production mode, it will be NodeJs client app which going to use the "API") I use jest to test it
My test code :
const WebSocket = require('ws');
test('Extension Connection test', async ()=>{
var ws = new WebSocket("ws://127.0.0.1:2031/Extension");
await new Promise((resolve, reject)=>{
ws.onmessage = function (message) {
var responseAttended = {"type":"Information","data":{"information":"Success connection as Extension"},"target":"Extension"};
assert(message.data, responseAttended);
resolve();
};
ws.onopen = function () {
var request = {target: "ExtensionService", type: "Auth", data: {name: "extension"}};
ws.send(JSON.stringify(request));
};
ws.onerror = function () {
assert(false);
reject();
}
});
});
I started my server before launch test, my server catch the value send (so websocket is connected). My server did some stuff and send back the response, but it's looks like this one never arrived in my NodeJs test client
As no response arrived, the promise is not resolved and after 5s the jest timeout stop the test.
Any idea what i made bad ?

I think you can use jest.setTimeout(millisecond) before use test(), it work for me
jest.setTimeout(10000);
test('Extension Connection test', async () => {
// TODO your code
})

Related

Websockets with RTK Query configuration issues

I am trying to implement a Websocket connection from a React TypeScript app using RTK query. At the moment I am just trying to connect to a local socket.io server BUT ultimately it will be an AWS API Gateway with Cognito auth. In any case I having some problems getting this to work as a simple starting point. I have a few elements at play that may be causing the issue/s:-
MSW is being used to intercept http requests to mock a restful API locally. I wonder if this is one of the issues
I am adding the Websocket as a query to an RTK Query createApi object with other queries and mutations. In reality the Websocket query will need to hit a different API Gateway to the one that is being set as the baseQuery baseUrl currently. Do I need to create a new and separate RTK Query api using createApi() for the Websocket query?
Anyhow, here is the server code:-
// example CRA socket.io from https://github.com/socketio/socket.io/blob/main/examples/create-react-app-example/server.js
const getWebsocketServerMock = () => {
const io = require('socket.io')({
cors: {
origin: ['http://localhost:3000']
}
});
io.on('connection', (socket: any) => {
console.log(`connect: ${socket.id}`);
socket.on('hello!', () => {
console.log(`hello from ${socket.id}`);
});
socket.on('disconnect', () => {
console.log(`disconnect: ${socket.id}`);
});
});
io.listen(3001);
setInterval(() => {
io.emit('message', new Date().toISOString());
}, 1000);
console.log('Websocket server file initialised');
};
getWebsocketServerMock();
export {};
My RTK Query api file looks like this:-
reducerPath: 'someApi',
baseQuery: baseQueryWithReauth,
endpoints: (builder) => ({
getWebsocketResponse: builder.query<WebsocketResult, void>({
query: () => ``,
async onCacheEntryAdded(arg, { updateCachedData, cacheDataLoaded, cacheEntryRemoved }) {
try {
// wait for the initial query to resolve before proceeding
await cacheDataLoaded;
const socket = io('http://localhost:3001', {});
console.log(`socket.connected: ${socket.connected}`);
socket.on('connect', () => {
console.log('socket connected on rtk query');
});
socket.on('message', (message) => {
console.log(`received message: ${message}`);
// updateCachedData((draft) => {
// draft.push(message);
// });
});
await cacheEntryRemoved;
} catch {
// no-op in case `cacheEntryRemoved` resolves before `cacheDataLoaded`,
// in which case `cacheDataLoaded` will throw
}
}
}),
getSomeOtherQuery(.....),
getSomeOtherMutation(....),
Any advice or thoughts would be greatly appreciated! I guess my main question is should I be able to combine the websocket query in the same createApi function with other queries and mutations that need to use a different baseQuery url as they need to hit different API Gateways on AWS?
Much thanks,
Sam
You can circumvent the baseQuery from being used by specifying a queryFn instead of query on your endpoint.
In the most simple version, that just returns null as data so you can modify it later - but if you have an initial websocket request you can also do that in the queryFn.
queryFn: async () => { return { data: null } },

mocha test not exiting when using websockets, even with websocket.close/termintae

I'm trying to test my websocket server, by opening a websocket client in my mocha test file, connection to the ws server and awaiting response. I send an http request to the ws server, and then a message is sent via websocket to the client, where I store the results and test it.
I get the response I want and the test passes, but the mocha test itself does not terminate and I'm forced to close it manually.
I have read this - explaining that there is probably still some async process open, such as an open socket, but I try to terminate the socket, and I get the 'close' event to fire (I get the console log message I defined in the 'close' event listener), but the test still isn't over.
I'm using ws (npm), mocha, chai (for asserts) and supertest (to invoke the server to send a response).
versions:
"ws": "^7.3.0",
"mocha": "^7.0.0",
"chai": "^4.2.0",
"supertest": "^4.0.2",
node: v12.9.1
I know I can use the --exit flag, as is suggested in this stack overflow answer, but I prefer not to, if it can be avoided.
Here is the relevant code:
'use strict';
const supertest = require('supertest');
const assert = require('chai').assert;
const paths = require('../common/paths');
const { sign } = require('../common/signing');
const WebSocket = require('ws');
describe.only('Events server tests', function () {
this.timeout(11000);
const docId = 'doc_events_' + Date.now();
const wsServerUrl = 'ws://localhost:8080/subscribe?params={"prefix_doc_id":"doc_events_"}';
const ws = new WebSocket(wsServerUrl);
let id_from_msg;
// Connection opened
ws.addEventListener('open', function (event) {
console.log('connection started!');
});
// Listen for messages
ws.addEventListener('message', function (event) {
console.log('\n\nMessage from server ', event.data, ' ', typeof event.data);
try {
// the msg recived via websocket is in the form of: "doc_id":X, and I store the docID in order to check if it matches the docId that was sent in the test.
if (event.data.includes('doc_id')) {
id_from_msg = JSON.parse(event.data).doc_id;
}
} catch (error) {
console.log('error: ', error);
}
});
ws.addEventListener('close', () => {
console.log('closed connection!');
});
before((done) => {
console.log('start');
done();
});
after((done) => {
ws.terminate();
// ws.close();
done();
console.log('after?');
});
it('Test 1 - send simple request to events server', (done) => {
const eventsUrl = paths.EVENTS.EVENTS();
const eventsObj = {
reqId: '',
docId: docId,
sessionId: 1,
status: 200
};
// This tests is used to invoke response from the socket server, and it works fine, the tests passes and ends without an issue.
supertest('http://localhost:3035')
.post(eventsUrl)
.set('Authorization', sign('post', eventsUrl, eventsObj))
.send(eventsObj)
.expect(200)
.expect(res => {
assert.ok(res.body);
assert.equal(id_from_msg, docId);
})
.end(done);
});
});
As you can see, I tried both ws.close() and ws.terminate() inside the "after" section, and both yield the same result: the test does not end, and the console.log('after?') line is fired after done() is called.
I tried to overwrite 'onclose' method and to fire in manually, but to no avail.
I tried also to close the websocket in the test itself (I mean in the 'it' section, even though I don't like it semantically) - but the test suit itself does not terminate, same as before.
Is there a way to make sure that the websocket is properly closed before done() is called in after?
I found a solution, and since its very weird, I post it here, so if anyone encounter something similar he/she can find help:
The core of the issue is that some async process is still open when the test is suppose to end, but it seems the in this test all the websockets are closed.
But here is the weird part - in this test it's true that all the ws are closed, in other test its not.
It found that I had another test with the same basic structure - in the "describe" section I had:
const docId = 'doc_events_' + Date.now();
const wsServerUrl = 'ws://localhost:8080/subscribe?params={"prefix_doc_id":"doc_events_"}';
const ws = new WebSocket(wsServerUrl);
And even if I had ".only" on the test I wanted to run, It seems that mocha runs all describes from all tests, event if there is an "only" flag on one of them.
Since the "describe" section of the other test was run, there was another open websocket, so the test was stuck.
That is a very weird behavior, and maybe I will contact the mocha team in the future about it, but for now - I hope this can help.

Whats the problem with the socketio connection?

Im having this alot of http petitions (6k INSIDE LAGGING) in 1-3 minutes in the console when i receive or send data to a socketio connection.
Im using node+express in the backend and vue on the front
Backend:
app.js
mongoose.connect('mongodb://localhost/app',{useNewUrlParser:true,useFindAndModify:false})
.then(result =>{
const server = app.listen(3000)
const io = require('./sockets/socket').init(server)
io.on('connection', socket =>{
// console.log('client connected')
})
if(result){console.log('express & mongo running');
}
})
.catch(error => console.log(error))
I created a io instance to use it on the routes
let io
module.exports = {
init: httpServer => {
io = require('socket.io')(httpServer)
return io;
},
getIo:()=>{
if(!io){
throw new Error('socket io not initialized')
}
return io;
}
}
Then, on the route, depending of the logic, the if,else choose what type socket response do
router.post('/post/voteup',checkAuthentication, async (req,res)=>{
//some logic
if(a.length <= 0){
io.getIo().emit('xxx', {action:'cleanAll'})
}
else if(b.length <= 0){
io.getIo().emit('xxx', {action:'cleanT',datoOne})
}
else{
io.getIo().emit('xxx', {action:'cleanX',dataTwo,dataOne,selected})
}
res.json({ serverResponse:'success'})
})
In the front (component) (activated with beforeUpdate life cycle hook)
getData(){
let socket = openSocket('http://localhost:3000')
socket.on('xxx', data => {
if(data.action === 'cleanX'){
if(this.selected === data.selected){
this.ddd = data.dataTwo
}
else if(!this.userTeamNickname){
this.qqq= data.dataOne
}
}
else if(data.action === 'cleanAll'){
this.ddd= []
this.qqq= []
}
else if(data.action === 'cleanT'){
this.ddd= data.dataOne
}
})
},
1. What kind of behavior can produce this such error?
2. Is any other most efficient way to do this?
It looks like socket.io is failing to establish a webSocket connection and has never advanced out of polling. By default, a socket.io connection starts with http polling and after a bit of negotiation with the server, it attempts to establish a webSocket connection. If that succeeds, it stops doing the polling and uses only the webSocket connection. If the the webSocket connection fails, it just keeps doing the polling.
Here are some reasons that can happen:
You have a mismatched version of socket.io in client and server.
You have some piece of infrastructure (proxy, firewall, load balancer, etc...) in between client and server that is not letting webSocket connections through.
You've attached more than one socket.io server handler to the same web server. You can't do that as the communication will get really messed up as multiple server handlers try to respond to the same client.
As a test, you could force the client to connect only with webSocket (no polling at all to start) and see if the connection fails:
let socket = io(yourURL, {transports: ["websocket"]})'
socket.on('connect', () => {console.log("connected"});
socket.on('connect_error', (e) => {console.log("connect error: ", e});
socket.on('connect_timeout', (e) => {console.log("connect timeout: ", e});

How to test socket.io server when client emits

I want a client to emit a signal, and test the behaviour of my socket.io server when that signal is received. I have looked at this question and these blog posts:
jest
mocha, chai
but they seem to be directed at testing the client, rather than the server.
Here is an example of something that I am trying to implement:
test('should communicate with waiting for socket.io handshakes', (done) => {
socket_client.emit('example', 'some messages');
setTimeout(() => {
socket_server.on('example', message => {
expect(message).toBe('INCORRECT MESSAGE');
});
done();
}, 50);
When I run my test suit, this should fail, but doesn't.
Does anyone have a simple example of testing this sort of behaviour?
Currently I'm using jest but any framework is fine.
My set up and teardown of the socket.io server test is as below:
import * as http from 'http';
import ioBack from 'socket.io';
let socket_client;
let httpServer;
let httpServerAddr;
let socket_server;
beforeAll((done) => {
httpServer = http.createServer().listen();
httpServerAddr = httpServer.address();
socket_server = ioBack(httpServer);
done();
});
afterAll((done) => {
socket_server.close();
httpServer.close();
done();
});
I am using mocha for testing. But I am not sure what you are doing. In your backend socket server there is no listener that you defined.
Here is a small example for a test. Maybe that helps?!
Test case
var sockethost = websocketUrl;
socketclient = io.connect(sockethost);
socketclient.on('customerId', function(data) {
var data = {
customerId: 10,
}
socketclient.emit('customerId', data);
});
Server:
var io = require('socket.io')(http);
io.sockets.on('connection', function (socket) {
socket.emit('customerId', null);
});
So that is a very simple test. The backend server sends out the connected client 'customerId' the client has a listener for customerId and sends back its customerId. You can also do the other way around, that you have a listener in server, and an emit in client. But I am not completely sure what you are trying to do.

How should I start my nodejs application automatically for tests

I have a nodejs restful style service which has no front end, it just accepts data and then does something with it.
I have unit tested most of the method level stuff I want to, however now I want to basically do some automated tests to prove it all works together. When I am using ASP.MVC and IIS its easy as the server is always on, so I just setup the scenario (insert dummy guff into DB) then make a HttpRequest and send it to the server and assert that I get back what I expect.
However there are a few challenges in nodejs as the applications need to be run via command line or some other mechanism, so given that I have an app.js which will start listening, is there some way for me to automatically start that going before I run my tests and then close it once my tests are finished?
I am currently using Yadda with Mocha for my testing so I can keep it written in a BDD style way, however I am hoping the starting of the web app is agnostic of the frameworks I am using.
Just expose some methods to start and stop your webserver. Your app.js file could be something like this:
var app = express()
var server = null
var port = 3000
// configure your app here...
exports.start = function(cb) {
server = http.createServer(app).listen(port, function () {
console.log('Express server listening on port ' + port)
cb && cb()
})
}
exports.close = function(cb) {
if (server) server.close(cb)
}
// when app.js is launched directly
if (module.id === require.main.id) {
exports.start()
}
And then in your tests you can do something like this (mocha based example):
var app = require('../app')
before(function(done) {
app.start(done)
})
after(function(done) {
app.close(done)
})
Have a look to supertest https://github.com/visionmedia/supertest
You can write test like
describe('GET /users', function(){
it('respond with json', function(done){
request(app)
.get('/user')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200, done);
})
})
Using gimenete's answer, here's an example of a service (server) with async await and express:
service.js:
const app = require('express')()
const config = require('./config')
let runningService
async function start() {
return new Promise((resolve, reject) => {
runningService = app.listen(config.get('port'), config.get('hostname'), () => {
console.log(`API Gateway service running at http://${config.get('hostname')}:${config.get('port')}/`)
resolve()
})
})
}
async function close() {
return new Promise((resolve, reject) => {
if (runningService) {
runningService.close(() => {
})
resolve()
}
reject()
})
}
module.exports = {
start,
close
}
service.spec.js:
const service = require('../service')
beforeEach(async () => {
await service.start()
})
afterEach(async () => {
await service.close()
})

Resources