Koa app hangs when tested with supertest - node.js

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.

Related

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.

Chai/Moka -> TypeError: request.get(...).expect is not a function

First time with TDD. I'm using the duo Chai/Moka after reading some article online for my NodeJS API.
I already made few dumb test to learn how to use those. Now I want test my API so I created a route :
app.get('/hello', function(req, res) {
res.status(200).send('Hello World!')
})
I try a test like this :
var request = require('superagent')
var expect = require('Chai').expect
[...]
describe('When request baseURL/hello', function(){
it('should salute you !', function (done) {
request
.get(baseURL + '/hello')
.expect(200)
.end(function(err, res){
if(err) return done(err)
done()
})
})
})
I have the fail output :
TypeError: request.get(...).expect is not a function
If I comment the expect line everything is working. I try this route with Postman and I have a 200 status code like expected.
I think you're using the wrong test module: you need supertest, not superagent.
Just install the supertest module, change the require line, and try again.

Mocha/SuperTest/Request Interaction Inconsistency - request.defaults is not a function

This one has me stumped. I'm using mochajs as a test suite for a NodeJS (Express 4) application. The test suite runs through a series of mocha files, all using a pretty similar set up,and when run individually they all run fine - but when run as a suite, one file suddenly fails with the error:
TypeError: request.defaults is not a function
at Object.module.exports.apiName (/my/system/file/path/repo/lib/reports.js:176:30)
All the files used to work, even when run as a suite, but we recently switched from using to use a config.json file as the environment determinant to using process.env.NODE_ENV - not sure if its related but the timing lines up.
A simplified overview of the file chain would look something like:
command line call:
env NODE_ENV=local mocha test
...runs through file1, file2, file3, etc until:
test/reports.js
var env = process.env.NODE_ENV,
config = require('../config.json');
// Libraries
var app = require('../app'), // Imports the application
http = require('http'), // Sets up the test server on a different port
assert = require('assert'), // Checks values against expected
request = require('supertest'); // Tests API Calls - https://www.npmjs.com/package/supertest
describe.skip('Reports', function() {
before(function(){
// Start APP and expose on a test different port
this.server = http.createServer(app).listen(3000);
// Set request path to server - needs to match above
request = request('http://localhost:3000');
});
describe('#NameOfAPI', function(){
it('should return {status:"error"} if invalid report requested (Requires Mongo Running)', function(done) {
this.slow(300);
request
.get('/app/reports/api/nameOfAPI?report=invalidReportName')
.expect(200)
.expect('Content-Type', /json/)
.end(function(err, res){
it(res.body)
assert.equal(null, err); // Ensure no errors in request
assert.equal('error', res.body.status); // Ensure correct status returned for invalid data
assert.notEqual(-1, res.body.messages[1].search(/empty response/i)); // Ensure proper message returned for invalid report
done();
}); // End HTTP request for invalid report
}); // End Test for invalid report
});
The request (via supertest) obviously calls the route (app/reports/api)->API (nameOfAPI) module:
lib/reports.js
var env = process.env.NODE_ENV,
config = require('../config.json'),
request = require('request');
var module.exports = {
requestReportToCsv: function(report_name, hostname, cb){
var req_limit = 100; // Set maximum concurrent requests - server returning numerous 'ECONNRESET' errors if hit too fast
// Set HTTP request defaults outside of any loops
// THIS IS THE LINE THAT'S FAILING - lib/reports.js:176:30
var http_request = request.defaults({ json: true, pool: { maxSockets: req_limit } });
if (env != 'prod') {
http_request = http_request.defaults({headers: {'dev-cookie': 'on', 'Cookie':'dev_cookie=on'}});
}
http_request.post('https://website.com/api/info.php', {form: post_data}, function(err, resp, body){
// Uncomment to see an example of the report response
...SO ON AND SO FORTH
Can't for the life of me figure out why request.defaults({}) will work fine when the mocha test file is called individually using env NODE_ENV=local mocha test/reports but fail when run as part of a suite using env NODE_ENV=local mocha test. Any ideas?
Edit: Note, renaming the var request = require('supertest') variable var supertest = require('supertest') (and all subsequent references) doesn't have an effect, so I don't think it's a scope/variable/name issue.

expect (js) not working inside superagent (js)

I'm trying to do TDD for the Rest APIs that I've been creating. New to NodeJS.
I've created a Rest API, and on the response I want to perform all the expect checks. To make an HTTP request I'm using SuperagentJS (also tried RequestJS).
Here is how my code looks like (Snippet only, not whole code)
var expect = require("chai").expect;
var request = require("superagent");
describe("Creation of New Entity", function(){
it("Create a New Entity", function(){
request
.get("http://localhost")
.end(function(err, httpResponse){
expect("1234").to.have.length(3);//equals(500);
expect(200).to.equals(200);
});
});
});
No matter what I try, mocha always gives successful result. (All test cases passed)
Please tell what I'm missing here. What should I do to implement test cases on httpRespnse. I'm sure that request is working fine, because whenever I use console.log(httpResponse.text), it is returning the default apache home page.
All networking in node.js is asynchronous, therefore you must use the mocha asynchronous flavor of it("Create a New Entity", function(done) { and call the done callback when your test is done.
var expect = require("chai").expect;
var request = require("superagent");
describe("Creation of New Entity", function(){
it("Create a New Entity", function(done){
request
.get("http://localhost")
.end(function(err, httpResponse){
expect(err).not.to.exist();
expect("1234").to.have.length(3);//equals(500);
expect(200).to.equals(200);
done()
});
});
});

Sails.js and Mocha: Using supertest to create a new model

I'm currently setting up testing infrastructure for my Sails app, and it was going smoothly until I tried testing API requests with supertest.
I'm trying to test some of my controller methods (that I implemented instead of using the default blueprint routes), but it seems like the API request isn't even going through. The reason I think this is because I can run npm test and this code will run fine, but if I change the POST path to /datamodel/create5, where create5() does NOT exist as a controller method, it still runs fine... In both cases, a DataModel model is NOT created. I've included some code below.
This is what my code looks like:
var request = require('supertest');
var assert = require('assert');
var async = require('async');
var stubs = require('../stubs.js');
describe('DataModel', function() {
var testDataModel;
var dataModelParams = stubs.dataModelStub(); // simply returns a JSON dictionary
describe('#create()', function() {
describe('data model import', function() {
it('should import a new data model.', function (done) {
var agent = request.agent(sails.hooks.http.app);
agent
.post('/datamodel/create')
.send(dataModelParams)
.expect(302)
.end(function (err, res) {
if (err) {
throw new Error(err);
}
console.log(res.dataModel);
DataModel.find().exec(function (err, dataModels) {
console.log(dataModels); // should return an array of 1 model but returns empty array instead
done();
});
});
});
});
});
Snippet of my controller code:
create: function(req, res) {
DataModel.create(req.params.all(), function dataModelCreated(err, dataModel) {
if (err) {
sails.log.debug(err);
}
FlashService.success(req, 'Successfully imported a new data model.');
fs.ensureDirSync(path.join(sails.config.paths.DATASET_EXTRACT_PATH, dataModel.fileSafeName));
fs.ensureDirSync(path.join(sails.config.paths.DATASET_DOWNLOAD_ROOT, 'non_pii', dataModel.fileSafeName));
fs.ensureDirSync(path.join(sails.config.paths.DATASET_DOWNLOAD_ROOT, 'pii', dataModel.fileSafeName));
fs.ensureDirSync(path.join(sails.config.paths.DATASET_ENCRYPT_PATH, dataModel.fileSafeName));
return res.redirect('/admin/manage_data_models');
});
}
Note that the create function runs correctly in practice when my app is launched. Any suggestions as to why my test isn't working properly? I'm using sails-memory for the tests if that helps.
I figured it out. I needed to authenticate my agent first (by making a call to the login route) before any of these calls would make it through.
Essentially:
var agent = request.agent(sails.hooks.http.app);
agent.post('YOUR_LOGIN_ROUTE').end(done);
// do your tests
Hmm, don't you need to pass something that looks like the sails app to supertest? There's an example here that shows what you need to do. Look at the before function in the second answer:
How to test controller using mocha in Sails?

Resources