Mock fs.readdir for testing - node.js

I'm trying to mock the function fs.readdir for my tests.
At first I've tried to use sinon because this is a very good framework for this, but is hasn't worked.
stub(fs, 'readdir').yieldsTo('callback', { error: null, files: ['index.md', 'page1.md', 'page2.md'] });
My second attempt was to mock the function with a self-replaced function. But it also doesn't works.
beforeEach(function () {
original = fs.readdir;
fs.readdir = function (path, callback) {
callback(null, ['/content/index.md', '/content/page1.md', '/content/page2.md']);
};
});
afterEach(function () {
fs.readdir = original;
});
Can anybody tell me why both doesn't works? Thanks!
Update - This also doesn't works:
sandbox.stub(fs, 'readdir', function (path, callback) {
callback(null, ['index.md', 'page1.md', 'page2.md']);
});
Update2:
My last attempt to mock the readdir function is working, when I'm trying to call this function directly in my test. But not when I'm calling the mocked function in another module.

I've found the reason for my problem. I've created the mock in my test class tried to test my rest api with supertest. The problem was that the test was executed in another process as the process in that my webserver runs. I've created the express-app in my test class and the test is now green.
this is test
describe('When user wants to list all existing pages', function () {
var sandbox;
var app = express();
beforeEach(function (done) {
sandbox = sinon.sandbox.create(); // #deprecated — Since 5.0, use sinon.createSandbox instead
app.get('/api/pages', pagesRoute);
done();
});
afterEach(function (done) {
sandbox.restore();
done();
});
it('should return a list of the pages with their titles except the index page', function (done) {
sandbox.stub(fs, 'readdir', function (path, callback) {
callback(null, ['index.md', 'page1.md', 'page2.md']);
});
request(app).get('/api/pages')
.expect('Content-Type', "application/json")
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
var pages = res.body;
should.exists(pages);
pages.length.should.equal(2);
done();
});
});
});

Related

Node.js: Run mocha tests in a request callback

I want to run some tests whose settings need to be downloaded first via a HTTP GET.
My download is successful but my test does not run when it's inside the request callback. I know it's not the best structure but I'd also like to know why this is not working.
describe('test', () => {
request.get({
url: 'https://google.com',
}, (err, status, body) => {
// The content is downloaded successfully.
console.log(body);
// This test never runs, why?
it('should be able to run inside a request.get', () => {
});
});
});
I know this code works but I would still like to know why the previous example does not.
describe('test', () => {
it('should be able to run inside a request.get', () => {
request.get({
url: 'https://google.com',
}, (err, status, body) => {
console.log(body);
});
});
});
EDIT: The suggestion provided by Jankapunkt's comment works: Moving the 'it' and 'describe' together allows for a successful download and test run.
request.get({
url: 'https://google.com',
}, (err, status, body) => {
// The content is downloaded successfully.
console.log(body);
// The describe and it are within the same closure.
describe('test', () => {
// This test runs successfully.
it('should be able to run inside a request.get', () => {
});
});
});
Old subject but I did like this for a Mock mongo connection:
const manager = require('./manager')
const assert = require('assert')
const MongoClient = require('./MockMongo')
let conn
describe('test execution', function() {
it('db connection', function (done) {
MongoClient.connect('test url')
.then((db) => {
conn = db
done()
})
})
it('test 1', function (done) {
manager.methodOfManager(conn, 'param1', 'param2')
.then(r => {
assert.equal(r.result, 'ok')
done()
})
.catch(err => {
console.log(err.message)
done(err)
})
})
})
It will print:
test execution
✓ db connection (5ms)
✓ Test 1 (sepa) (125ms)
2 passing (0s)
Approach 1: Tests inside your GET result
Use describe() around the it() within the callback function and avoid arrow functions like the following:
it("...", () => {});
It is discouraged in mocha when you have changing contexts.
Instead use
it("...", function(){});
and use .bind , done() or promises when required in async tests.
Putting this together into your code example, you may find your code to be similar to the following:
request.get({
url: 'https://google.com',
}, (err, status, body) => {
// The content is downloaded successfully.
describe('test', function() {
it('should be able to run inside a request.get', function() {
assert.isDefined(status);
assert.isDefined(body);
//...and so on
});
});
});
By the way - it is only a bad structure if for your approach is a better structure available.
Approach 2 - Wrap request.get in your unit
This is (in my opinion) the better and more mocha-like approach, where you execute the request inside the test and use the done() callback to notify mocha, that you are done:
describe('test', function() {
let request;
beforeEach(function(){
// request = ...
});
it('should be able to get a request result', function(done) {
request.get({
url: 'https://google.com',
}, (err, status, body) => {
assert.isDefined(status);
assert.isDefined(body);
//...and so on
// at the end call done
done();
});
});
You can ensure that request is initialized each test as a new fresh instance by using the beforeEach hook.

Mocha tests not failing when they should

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

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