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.
Related
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
})
I am writing a grapqhl server that has a simple logout mutation. Everything works as expected when I run the server and I can log out by destroying the session and clearing the cookie just fine.
Here is the resolver:
export default async (root, args, context) => {
console.log("THIS WILL LOG")
await new Promise((res, rej) =>
context.req.session.destroy(err => {
if (err) {
return rej(false);
}
context.res.clearCookie("qid");
return res(true);
})
);
console.log("NEVER HERE BEFORE TIMEOUT");
// 4. Return the message
return {
code: "OK",
message: "You have been logged out.",
success: true,
item: null
};
};
I am attempting to write a simple test just to verify that the req.session.destroy and res.clearCookie functions are actually called. At this point I AM NOT attempting to test if a cookie is actually cleared, as I am not actually starting up the server, I am just testing that the graphql resolver was ran correctly and that it called the right functions.
Here is a portion of my test:
describe("confirmLoginResolver", () => {
test("throws error if logged in", async () => {
const user = await createTestUser();
const context = makeTestContext(user.id);
context.req.session.destroy = jest
.fn()
.mockImplementation(() => Promise.resolve(true));
context.res.clearCookie = jest.fn();
// this function is just a helper to process my graphql request.
// it does not actually start up the express server
const res = await graphqlTestCall(
LOGOUT_MUTATION, // the graphql mutation stored in a var
null, // no variables needed for mutation
null // a way for me to pass in a userID to mock auth state,
context // Context override, will use above context
);
console.log(res);
expect(context.req.session.destroy).toHaveBeenCalled();
// expect(res.errors.length).toBe(1);
// expect(res.errors).toMatchSnapshot();
});
});
Again, everything works correctly when actually running the server. The problem is that when I attempt to run the above test, I always get a jest timeout:
Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
The reason is that the await section of above resolver will hang because it's promise.resolve() is never being executed. So my console will show "THIS WILL LOG", but will never get to "NEVER HERE BEFORE TIMEOUT".
I suspect I need to write a better jest mock to more accurately simulate the callback inside of context.req.session.destroy, but I can not figure it out.
Any ideas how I can write a better mock implementation here?
context.req.session.destroy = jest
.fn()
.mockImplementation(() => Promise.resolve(true));
Is not cutting it. Thoughts?
Try
context.req.session.destroy = jest
.fn()
.mockImplementation((fn) => fn(false));
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.
I'm trying to write some tests using Lab and Sinon for various HTTP requests that are called in a file of mine. I followed the Fake XMLHttpRequest example at http://sinonjs.org/ but when I run my tests it appears to not actually capture any requests.
Here is the (relevant) testing code:
context('when provided a valid payload', function () {
let xhr;
let requests;
before(function (done) {
xhr = sinon.useFakeXMLHttpRequest();
requests = [];
xhr.onCreate = function (req) { requests.push(req); };
done();
});
after(function (done) {
// clean up globals
xhr.restore();
done();
});
it('responds with the ticket id', (done) => {
create(internals.validOptions, sinon.spy());
console.log(requests); // Logs empty array []
done();
});
});
create is the function I imported from the other file, here:
internals.create = async function (request, reply) {
const engineeringTicket = request.payload.type === 'engineering';
const urgentTicket = request.payload.urgency === 'urgent';
if (validation.isValid(request.payload)) {
const attachmentPaths = formatUploads(request.payload.attachments);
const ticketData = await getTicket(request.payload, attachmentPaths);
if (engineeringTicket) {
const issueData = getIssue(request.payload);
const response = await jira.createIssue(issueData);
jira.addAttachment(response.id, attachmentPaths);
if (urgentTicket) {
const message = slack.getMessage(response);
slack.postToSlack(message);
}
}
zendesk.submitTicket(ticketData, function (error, statusCode, result) {
if (!error) {
reply(result).code(statusCode);
} else {
console.log(error);
}
});
} else {
reply({ errors: validation.errors }).code(400); // wrap in Boom
}
};
as you can see it calls jira.createIssue and zendesk.submitTicket, both of which use an HTTP request to post some payload to an API. However, after running the test, the requests variable is still empty and seems to have captured no requests. It is definitely not actually submitting the requests as no tickets/issues have been created, what do I need to fix to actually capture the requests?
Your problem is apparent from the tags: you are running the code in NodeJS, but the networking stubs in Sinon is for XMLHttpRequest, which is a browser specific API. It does not exist in Node, and as such, the setup will never work.
That means if this should have worked you would have needed to run the tests in a browser. The Karma test runner can help you with this if you need to automate it.
To make this work in Node you can either go for an approach where you try to stub out at a higher level - meaning stubbing the methods of zendesk and jira, or you can continue with the approach of stubbing network responses (which makes the tests a bit more brittle).
To continue stubbing out HTTP calls, you can do this in Node using Nock. Saving the requests like you did above is done like this:
var requests = [];
var scope = nock('http://www.google.com')
.get('/cat-poems')
.reply(function(uri, requestBody) {
requests.push( {uri, requestBody} );
});
To get some insights on how you can stub out at a higher level, I wrote this answer on using dependency injection and Sinon, while this article by Morgan Roderick gives an intro to link seams.
My supertest / tape test file looks like this:
var test = require('tape');
var app = require('../../api');
var agent = require('supertest').agent
var supertestCompatibleServer = agent(app.callback());
test('GET /Campus.svc', function (t) {
supertestCompatibleServer
.get('/Campus.svc')
.expect(200)
.expect('Content-Type', /json/)
.end(function (err, res) {
t.ifError(err, 'No error');
t.end();
});
});
The endpoint I'm testing works fine when starting the server and manually hitting it with curl or the browser.
The tests run fine and pass, but they just hang at the end instead of finishing.
The actual endpoint code just hits the database and returns some records as json.
What could be causing the tests to hang and how can I fix it?
This turned out to be related to this issue: https://github.com/substack/tape/issues/216
In my case, the database connection via knex was still open, which was causing node process to finish. The solution was to explicitly call knex.destroy() in a teardown test.