Mocha tests not failing when they should - node.js

I'm trying to test my routes file, and mocha is returning success for all of my expects, even though I've coded a couple that should absolutely fail. I added a 2+2 = 5 test just to make sure something would fail. I have done() in my assertion blocks.
I'm using a MEAN stack, and I tried to test the node files with jasmine, since I'm already using that to test the Angular files, but got tons of crazy errors, so I threw all that out and decided to give mocha a try instead.
Results:
Routes
1) makes sure something fails
GET /
√ returns status code 200
GET /nonexistent
√ returns status code 400
GET /api/todos
√ returns status code 200
√ returns a list of todos
Test file
// test/routes.spec.js
var request = require('request');
var expect = require('chai').expect;
describe('Routes', function() {
var base_url = "http://localhost:8080/"
// does fail as expected
it("makes sure something fails", function () {
expect(2 + 2).to.equal(5);
});
describe("GET /", function() {
it("returns status code 200", function() {
request(base_url, function(error, response, body) {
expect(response.statusCode).to.equal(200);
done();
});
});
});
//should fail
describe("GET /nonexistent", function() {
it("returns status code 400", function () {
request(base_url + "/nonexistent", function (error, response, body) {
expect(response.statusCode).to.equal(200);
done();
});
});
});
describe("GET /api/todos", function() {
it("returns status code 200", function() {
request(base_url + "/api/todos", function(error, response, body) {
expect(response.statusCode).to.equal(200);
done();
});
});
//should fail
it("returns a list of todos", function() {
request(base_url + "/api/todos", function(error, response, body) {
console.log(body);
expect(body).to.equal("abcd");
done();
});
});
});
});
Routes file:
// app/routes.js
var Todo = require('./models/todo');
module.exports = function(app) {
// api ---------------------------------------------
// get all todos
app.get('/api/todos', function (req, res) {
Todo.find(function (err, todos) {
if (err)
res.send(err)
res.json(todos);
});
});
// create todo and send back all todos after creation
app.post('/api/todos', function (req, res) {
Todo.create({
text: req.body.text,
done: false
}, function (err, todo) {
if (err)
res.send(err);
Todo.find(function (err, todos) {
if (err)
res.send(err)
res.json(todos);
});
});
});
// delete a todo
app.delete('/api/todos/:todo_id', function (req, res) {
Todo.remove({
_id: req.params.todo_id
}, function (err, todo) {
if (err)
res.send(err);
Todo.find(function (err, todos) {
if (err)
res.send(err)
res.json(todos);
})
})
})
// application --------------------------------------
app.get('*', function (req, res) {
res.sendFile(__dirname + '/public/index.html');
});
};

You want to use the done callback but none of your tests declare it in the parameters of the callbacks passed to it. Your first test, for instance, should be:
it("returns status code 200", function (done) { // <== Add parameter here!
request(base_url, function(error, response, body) {
expect(response.statusCode).to.equal(200);
done();
});
});
Without the parameter, Mocha considers the test to be synchronous. So it does not wait for request to call its callback, and ends right away. The fact that done is undefined does not lead to an error because the JavaScript interpreter does not get to done() before Mocha deems the tests over.

I'm a JavaScript novice and had to change my code from
it('getReports', () => {
getReports()
.then((res) => {
assert.equal(200, res.statusCode);
});
});
to
it('getReports', () => getReports()
.then((res) => {
assert.equal(200, res.statusCode);
}));
i.e. Had to remove the first set of curly brackets.
After this the Mocha tests reported an error.

Starting with Node 8 you can use the native async/await approach for requests and testing.
First use request-promise or request-promise-native instead request.
const request = require('request-promise-native');
Tests with async/await:
// testing success results - any error will fail the test
it('Returns status code 200', async () => {
const response = await request(base_url);
expect(response.statusCode).to.equal(200);
});
// testing for a particular error
it('Testing a particular error is thrown', async () => {
let error;
try {
await request(base_url);
} catch (err) {
error = err;
}
expect(error).to.be.ok;
expect(error.message).to.equal('Expected error message');
});

In my case running test files with the below command solved the problem.
node --unhandled-rejections=strict node_modules/.bin/mocha --require #babel/register --require babel-polyfill test/**/*.test.js

Related

Node JS returns undefined within async json call

I have an asynchronous function that is called when the api is connected to. this should return some json and then it will be displayed on the json response of the page. In the json response I get undefined.
This is the code i am using:
const express = require('express');
const router = express.Router();
const superagent = require('superagent');
function getCyrpto(){
var result;
superagent.get('https://min-api.cryptocompare.com/data/v2/pair/mapping/exchange?e=Kraken')
.query({ api_key: 'xxxxxxxx'})
.end((err, res) => {
if (err) { return console.log(err); }
result = res.body;
});
setTimeout(() => {
console.log(result);
return result;
}, 2000)
}
router.get('/', (req, res, next) => {
crypto=getCyrpto()
setTimeout(()=> {
res.status(200).json({
message: 'geting cyrpto',
apiResponse: crypto
});
}, 2500)
});
The reason it is happeing because your setTimeOut methods runs before your api call get the result and assign it to the result.
This is a common problem most of us face when we start to learn concurrency concept.
For example:
console.log("a");
setTimeOut(()=>console.log("b"),1000);
console.log("c");
Output of above function will
a
c
b
this is happening beacause setTimeout function is a promise which means your nodejs will not wait for it to finish before running the next line, it will just process the setTimeout function in background and when it will finish it will call its callback function given as first parameter in setTimeOut.
Your solution should be
function getCyrpto(){
return new Promise((resolve,reject)=>{
var result;
superagent.get('https://min-api.cryptocompare.com/data/v2/pair/mapping/exchange?e=Kraken')
.query({ api_key: 'xxxxxxxx'})
.end((err, res) => {
if (err) { console.log(err); reject(err); }
result = res.body;
setTimeout(() => {
console.log(result);
resolve(result);
}, 2000)
});
}
router.get('/', (req, res, next) => {
getCyrpto().then(crypto=>{
setTimeout(()=> {
res.status(200).json({
message: 'geting cyrpto',
apiResponse: crypto
},2500);
}).catch(err=>{res.status(400).json(err)})
}

Mocha, how to test async code without UnhandledPromiseRejectionWarning

I'm testing my server-side api endpoints with mochajs and I can't figure out how to do it properly.
I started with code that had the following logic:
it('test', (doneFn) => {
// Add request handler
express.get('/test', (req, res, next) => {
// Send response
res.status(200).end();
// Run some more tests (which will fail and throw an Error)
true.should.be.false;
// And that's the problem, normally my framework would catch the
// error and return it in the response, but that logic can't work
// for code executed after the response is sent.
});
// Launch request
requests.get(url('/test'), (err, resp, body) => { // Handle response
// I need to run some more tests here
true.should.be.true;
// Tell mocha test is finished
doneFn();
});
});
But the test doesn't fail because it throws in the request handling callback.
So I googled around and found that my problem could be solved using promises, and it does, now the test fails. This is the resulting code:
it('test', (doneFn) => {
let handlerPromise;
// Add request handler
express.get('/test', (req, res, next) => {
// Store it in a promise
handlerPromise = new Promise(fulfill => {
res.status(200).end();
true.should.be.false; // Fail
fulfill();
});
});
// Send request
requests.get(url('/test'), (err, resp, body) => {
// Run the other tests
true.should.be.true;
handlerPromise
.then(() => doneFn()) // If no error, pass
.catch(doneFn); // Else, call doneFn(error);
});
});
But now I end up with a deprecation warning because the error is handled in a different process tick than the one it was thrown.
The errors are: UnhandledPromiseRejectionWarning and PromiseRejectionHandledWarning
How can I make my test fail after the response is sent, and avoid having an
unhandledPromiseRejectionWarning?
This works
it('test', (doneFn) => {
let bindRequestHandler = new Promise((reslove, reject) => {
// Add request handler
app.testRouter.get('/test', (req, res, next) => {
// Send response
res.status(200).end();
try { // Here, we need a try/catch/reject logic because we're in a callback (not in the promise scope)
// Run some more tests (which will fail and throw an Error)
true.should.be.false;
} catch (error) { // Catch the failing test errors and reject them
reject(error);
}
resolve();
});
});
let sendRequest = new Promise((reslove, reject) => {
// Launch request
requests.get(url('/test'), (err, resp, body) => { // Handle response
try {
// I need to run some more tests here
true.should.be.true;
} catch (error) { // Catch the failing test errors and reject them
reject(error);
}
reslove();
});
});
Promise.all([bindRequestHandler, sendRequest])
.then(res => doneFn())
.catch(doneFn);
});

Jasmine Testing Node.js Async modules

I\m trying to write unit tests for some code I wrote, the problem I'm running into is I expect my mock callback to be called after executing the function but my test fails as it is never called.
describe("Asynchronous specs", function() {
var mockNext;
beforeEach(function() {
mockNext = jasmine.createSpy('mockNext');
var res;
parallelRequests.APICall(testObject[0], null, mockNext);
});
it("callback spy should be called", function () {
expect(mockNext).toHaveBeenCalled();
});
});
The function being tested is quite simple:
function APICall(options, res, next) {
request(options, callback);
function callback(error, response, body) {
if (error) {
if (error.code === 'ETIMEDOUT') {
return logger.error('request timed out: ', error);
next(error);
}
logger.error('request failed: ', error);
next(error);
}
next(null);
}
}
The issue I suspect is jasmine testing the expectation before the mock callback is executed in API Call due to request's async nature. I've tried using what others suggest of using done() and flags but with no luck. Would appreciate some guidance in this matter.
Your beforeEach code is asynchronous. You have to tell yasmin when your beforeEach logic is done. You can solve this by the callback method done, that is passed to each test. Try this:
describe("Asynchronous specs", function() {
var mockNext;
beforeEach(function(done) {
parallelRequests.APICall(testObject[0], null, function(){
mockNext = jasmine.createSpy('mockNext');
mockNext();
done();
});
});
it("callback spy should be called", function () {
expect(mockNext).toHaveBeenCalled();
});
});

Why my Supertest test that should be failing is passing?

I'm starting my project based on this repo:
https://github.com/madhums/node-express-mongoose
The first thing I decided to do was write tests, so I went to the test file, and it looks something like this:
var mongoose = require('mongoose');
var should = require('should');
var request = require('supertest');
var app = require('../server');
var context = describe;
// other stuff you want to include for tests
before(function (done) {
// clear db and other stuff
done();
});
describe('Users', function () {
describe('POST /users', function () {
it('should create a user', function (done) {
request(app)
.post('/users')
.field('name', 'foo')
.field('email', 'foo')
.field('password', 'foo')
.expect('Content-Type', /json/)
.expect(200)
.end(function(err, res){
console.log(err);
if(err){
console.log("error");
}
// console.log(res);
});
done();
});
});
});
after(function (done) {
// do some stuff
done();
});
I haven't actually created the route, so the test should be failing, and I even do get an error on .end, so any idea why is the test not failing?
My console shows me this when I run npm test
Express app started on port 3000
Users
POST /users
✓ should create a user
[Error: expected "Content-Type" matching /json/, got "text/html; charset=utf-8"]
error
1 passing (653ms)
The problem is that your test is asynchronous, but your code is synchronous. Specifically, the done() line is always called.
You need to move the done() line into the .end block, and add done(err) for the error case, like this:
.end(function(err, res){
if(err){
console.log("error");
done(err);
}
else {
console.log(res);
done();
}
});

Supertest + Express won't fail

This is more or less a duplicate of supertest test express middleware
but after a year, I figured I'd start a new question.
var express = require('express');
var request = require('supertest');
var app1 = express();
app1.get('/myapp', function (req, res) {
res.send(200, { name: 'myapp' });
});
request = request(app1);
it('should fail', function () {
request
.get('/hahahahahahahaha')
.expect(123);
});
As far as I can tell, that will always erroneously pass. The fact that the path is wrong and is expecting a different status code doesn't matter.
And - more generically (without Express), it looks like this always passes, also:
it('should fail', function () {
request('http://thisdoesnotexist.mydomain')
.get()
.expect(200);
});
This doesn't work either:
it('should fail', function () {
request('http://thisdoesnotexist.mydomain')
.get()
.expect(200)
.end(function (err, res) {
if (err) {
throw err;
}
});
});
Any thought as to why this happens, or how to actually test such a scenario?
With supertest you need to terminate your chain somehow.
expect will take a finished callback as the second parameter, and you can use the build in mocha callback for this. Like so:
describe('potato', function() {
it('should fail', function(done) {
request
.get('/hahahahahahahaha')
.expect(123, done);
});
});
Specifying a done option like this will instruct mocha to wait until it's heard back from you before proceeding to the next test.
The difference is the parameter: done
describe('XXX', function() {
it('XXX', function() {
// always passing
})
})
describe('YYY', function(done) {
it('YYY', function() {
// always passing
})
})
describe('ZZZ', function() {
it('ZZZ', function(done) {
// normal
})
})

Resources