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

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.

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.

Sinon Fake XML Not Capturing Requests

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.

How to create request and response objects in node

I'm trying to mock request and response objects for my node/express handlers. I've tried a few mocking libraries and have run into issues with API compatibility, which has made them too unreliable for testing purposes.
What I would like to do is create the raw request and response objects myself and direct the output somewhere other than a live connection.
Here's what I have so far:
env.mockReq = function(o){
o = o || {};
o.hostname = 'www.tenor.co';
o.protocol = 'https';
o.path = o.url;
o.createConnection = function(){
console.log('mockReq createConnection');
};
var req = new http.ClientRequest(o);
req.url = o.url;
req.method = o.method;
req.headers = o.headers || {};
return req;
};
env.mockRes = function(o){
var res = new http.ServerResponse({
createConnection: function(){
console.log('mockRes createConnection');
}
});
return res;
};
Here's some test code:
var req = env.mockReq({method: 'GET', url: '/'});
var res = env.mockRes();
res.on('end', function(arguments){
expect(this.statusCode).toBe(200);
expect(this._getData().substr(-7)).toEqual('</html>');
scope.done();
done();
});
// my express app
app.handle(req, res);
My handler is piping a stream data source to the response:
stream.pipe(response);
It works fine when I load the requests in a browser, but my test times out because the response end event never gets fired. I should note that I have logging statements in my handler that's under test and it completes right to the end.
To complicate matters, I'm using nock to mock out some API requests. I had to add the following to prevent an error:
// Prevents "Error: Protocol "https" not supported. Expected "http:""
nock('http://www.example.com')
.persist()
.get('/non-existant-path')
.reply(function(uri, requestBody) {
console.log('nock path:', this.req.path);
return ''
});
That nock callback never actually gets called though. But without this code I get that error, even if I don't use https. The live version of my site redirects all traffic to https, so maybe a live connection is being made, but then why is my handler executing?

Koa app hangs when tested with supertest

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.

Can I change the output of jasmine-node to show the passing tests in the console as well as the failing?

I have the following file that runs when I run node test.js.
var Jasmine = require('jasmine');
var jasmine = new Jasmine();
var request = require('request');
describe("test", function(){
it("works", function(){
expect(5 + 2).toEqual(4);
});
it("should respond with hello world", function(done){
request('http://localhost:3000/', function(err, res, body){
expect(body).toEqual('hello world');
done();
})
});
})
jasmine.execute();
And this gives me the following output:
Started
F.
Failures:
1) test works
Message:
Expected 7 to equal 4.
Stack:
Error: Expected 7 to equal 4.
at Object.<anonymous>
2 specs, 1 failure
Finished in 0.037 seconds
Obviously one fails, showing the F, and one passes, showing the dot. Can I change this configuration to have it show both the passing and the failing tests?
You'll want to use a custom reporter. I recommend using jasmine-console-reporter, which will give you nicely formatted output that will include all tests that ran (not just the failed ones). Your original script would change to the following:
var Jasmine = require("jasmine");
var jasmine = new Jasmine();
var request = require('request');
// Register a Custom Reporter
const Reporter = require('jasmine-console-reporter');
jasmine.jasmine.getEnv().addReporter(new Reporter());
describe("test", function(){
it("works", function(){
expect(5 + 2).toEqual(4);
});
it("should respond with hello world", function(done){
request('http://localhost:3000/', function(err, res, body){
expect(body).toEqual('hello world');
done();
})
});
})
jasmine.execute();
Note that if you are using the jasmine command line to run the tests (and so Jasmine has exported its helpers into your namespace), your code would be as follows:
const Reporter = require('jasmine-console-reporter');
jasmine.getEnv().addReporter(new Reporter());
I personally find it easiest to use that with Gulp and gulp-jasmine to make the definition clear and in one place, while also allowing me to run build steps prior to the tests:
const gulp = require('gulp');
const jasmine = require('gulp-jasmine');
const Reporter = require('jasmine-console-reporter');
gulp.task('default', function() {
gulp.src('spec/**/*.js')
.pipe(jasmine({
reporter: new Reporter()
}));
});

Resources