What is the best way to write unit test in node.js? - javascript

I have a module where I load a mustache template file. I would like to write a unit test for this. I am trying to use mocha, chai and rewire.
Here is my module.js:
var winston = require('winston');
var fs = require('fs');
var config = require('./config.js');
exports.logger = new winston.Logger({
transports: [
new winston.transports.File(config.logger_config.file_transport),
new winston.transports.Console(config.logger_config.console_transport)
],
exitOnError: false
});
exports.readTemplateFile = function(templateFile, callback) {
fs.readFile(config.base_directory + templateFile + '.tpl.xml', 'utf8', function (err, data) {
if (err) {
logger.error('Could not read template ' + templateFile + ': ' + err);
}
callback(data);
});
};
In the callback function I use mustache to do something with the template.
What is the best way to test this?
Maybe I will have to rewire the fs.readFile? As the file won't be there when the test will be executed. The Winston logger is also an interesting part I guess, not sure if it will be initialized if I import this within a mocha test. My first test shows the logger is undefined.

One of the most important unit testing principle is testing very small piece of code. To achieve that you should definitely mock or stub calls to functions that not belong to testing code (readFile and logger.error in this case). For provided code you can make three test cases:
calling readFile with proper arguments
calling error if err is present
calling callback function with proper argument
Your callback function should be tested outside this code, for e.g. by providing fake data as parameter:
define('Some test', () => {
it('should return true', () => {
expect(callbackFunction('fakeData').to.be.ok);
});
});

Related

How to modify test results' output in mocha.js (or another library)

I am creating a test suite that will be run by mocha. In the test, I plan on using should and chai at the very least. I am testing JSON objects. I want mocha to be silent about successful tests and I want to control what the errors look like at the end of the run. Right now, there is a lot of red text describing the JSON object and other information. What I want is to override the output.
Here is an example test for an element in the JSON object which I would import from testData.
var should = require("should");
var chai = require("chai");
var expect = chai.expect;
const testData = require("./testData");
for (t of testData.data) {
describe("This will fail", function() {
it("Should have a zipperNut",function(done) {
t.should.have.property('zipperNut');
done();
});
});
}
What I want is to see just this:
$ mocha testSuite.js
zipperNut
$
And so on for all the tests I want to run. Can I do this? Do I need to use a different library?
Mocha's output is determined by something called a "default reporter". If you want to change the output you can specify a different reporter, which is presumably available through npm or similar. To specify a new custom reporter you do this:
$ mocha --reporter my-reporter.js
Where my-reporter.js is the file containing my "reporter". For me, I just want the plain name of the failing tests, so my reporter would look like:
'use strict';
const Mocha = require('mocha');
const {
EVENT_RUN_END,
EVENT_TEST_FAIL,
} = Mocha.Runner.constants;
class MyReporter {
constructor(runner) {
const stats = runner.stats;
runner
.on(EVENT_TEST_FAIL, (test, err) => {
console.log(
`${test.title}`
);
})
.once(EVENT_RUN_END, () => {
console.log(`end: ${stats.passes}/${stats.passes + stats.failures} ok`);
});
}
}
module.exports = MyReporter;
Documentation here:
https://mochajs.org/api/tutorial-custom-reporter.html

node.js mock sendmailer transporter inside function

Im working with an node handler in AWS lambda and i need to make another files with integration tests from that function, but i cant mock the transporter with sinon or mockery.
the index.js function:
var nodemailer = require('nodemailer');
exports.handler = (event, context, callback) =>
{
var transporter=createTransporter();
transporter.sendMail(data, function (error, success) {
console.log(error);
response = getResponse(404, error);
}
callback(null, response);
});
}
function createTransporter() {
return nodemailer.createTransport({
service: "SMTP",
auth: {
user: "XXXX#XXX",
pass: "XXXX"
}
});
}
the purpose is to mock the function createTransporter() so that it doesnt send any email when it is called in javascript file test with mocha and expect:
var mockery = require('mockery');
var nodemailerMock = require('nodemailer-mock');
var index = require("../index.js");
describe("The handler function tests", function () {
before(function () {
mockery.enable({
warnOnUnregistered: false
});
mockery.registerMock('nodemailer', nodemailerMock);
});
it('JSON error html ', function () {
var callback = function (name, response) {
expect(JSON.stringify(response.statusCode)).to.be('404');
};
var context = {};
index.handler(event, context, callback);
});
});
I wrote nodemailer-mock :)
The problem you're having is that you are calling var index = require("../index.js"); before you are mocking nodemailer via mockery, so it is already in the module cache. I included // Make sure anything that uses nodemailer is loaded here, after it is mocked... in the examples in the README, but should probably make it more clear.
Move the require("../index.js") after nodemailer is mocked and it will be work as expected.
var mockery = require('mockery');
var nodemailerMock = require('nodemailer-mock');
// don't require here since you will get the real nodemailer and cache it
var index;
describe("The handler function tests", function () {
before(function () {
mockery.enable({
warnOnUnregistered: false
});
mockery.registerMock('nodemailer', nodemailerMock);
// do the require() here after nodemailer is mocked
index = require("../index.js");
});
// your tests here should now use nodemailer-mock
it('JSON error html ', function () {
var callback = function (name, response) {
expect(JSON.stringify(response.statusCode)).to.be('404');
};
var context = {};
index.handler(event, context, callback);
});
});
Another option is to use the { useCleanCache: true } option with calls to mockery.resetCache();, though I have had mixed results. See Controlling the Module Cache in the mockery documentation.
I'm not 100% sure why this would fail, but I suggest one of two things:
Try doing var createTransporter = function()... there's a slight difference here that might be your issue
exporting createTransporter so you can assign a new value to it, either a mock or not. This isn't very "keep implementation details private", it does work
Have your module return a class, or object anyway, where you can set some "use this transporter method" value. (ie dependency injection)
You can use the following option from Jest:
jest.mock('nodemailer').setMock(/* function mock for module */)
Remember to use this at the top of the file, before import or require statements.
Here is the official Jest documentation: https://jestjs.io/docs/manual-mocks#mocking-node-modules.

Sinon - How to stub authentication library (Authy -Twilio)

I am currently new to Sinon, Mocha, Supertest and in the process to writes tests. In my current scenario, i have authentication library which verifies my "OTP" and after verifying it proceeds to do operation within the callback function.
I am unable to mock the callback to return null and carry on to test rest of the code. Following is my code snippet:
Controller.js
var authy = require('authy')(sails.config.authy.token);
authy.verify(req.param('aid'), req.param('oid'), function(err, response) {
console.log(err);
if (err) {
return res.badRequest('verification failed.');
}
....
My test is :
var authy = require('authy')('token');
describe('Controller', function() {
before(function() {
var authyStub = sinon.stub(authy, 'verify');
authyStub.callsArgWith(2, null, true);
});
it('creates a test user', function(done) {
// This function will create a user again and again.
this.timeout(15000);
api.post('my_endpoint')
.send({
aid: 1,
oid: 1
})
.expect(201, done);
});
});
I essentially want to call authy verify get a null as "err" in callback, so i can test the rest of the code.
Any help would be highly appreciated.
Thanks
The trouble is that you're using different instances of the authy object in your tests and your code. See here authy github repo.
In your code you do
var authy = require('authy')(sails.config.authy.token);
and in your test
var authy = require('authy')('token');
So your stub is generally fine, but it will never work like this because your code does not use your stub.
A way out is to allow for the authy instance in your controller to be injected from the outside. Something like this:
function Controller(authy) {
// instantiate authy if no argument passed
in your tests you can then do
describe('Controller', function() {
before(function() {
var authyStub = sinon.stub(authy, 'verify');
authyStub.callsArgWith(2, null, true);
// get a controller instance, however you do it
// but pass in your stub explicitly
ctrl = Controller(authyStub);
});
});

Mocking console.log()/Any Other Function in MOCHA testing framework

I am writing test cases for NODE JS API. But wherever console.log() is there in routes or services of NODE JS File, it gets printed to CLI. Is there a way to mock these so that these won't get printed in CLI.
I have explored couple of libraries like Sinon, Stub for mocking. But couldn't grasp the working of those libraries.
You can override function entirely: console.log = function () {}.
You should not try to mock console.log itself, a better approach is for your node modules to take a logging object. This allows you to provide an alternative (ie. a mock) during testing. For example:
<my_logger.js>
module.exports = {
err: function(message) {
console.log(message);
}
}
<my_module.js>
var DefaultLogger = require('my_logger.js');
module.exports = function(logger) {
this.log = logger || DefaultLogger;
// Other setup goes here
};
module.exports.prototype.myMethod = function() {
this.log.err('Error message.');
};
<my_module_test.js>
var MyModule = require('my_module.js');
describe('Test Example', function() {
var log_mock = { err: function(msg) {} };
it('Should not output anything.', function() {
var obj = new MyModule(log_mock);
obj.myMethod();
});
});
The code here I've simplified, as the actual test isn't the reason for the example. Merely the insertion of alternative logging.
If you have a large codebase with lots of console.log calls, it is better to simply update the code as you add tests for each method. Making your logging pluggable in this way makes your code easier and more receptive to testing. Also, there are many logging frameworks available for node. console.log is fine during development when you just want to dump out something to see what's going on. But, if possible, try to avoid using it as your logging solution.
I could not find a solution which only hides the console.log calls in the module to be tested, and mocks none of the calls of the testing framework (mocha/chai in my case).
I came up with using a copy of console in the app code:
/* console.js */
module.exports = console;
/* app.js */
const console = require('./console');
console.log("I'm hidden in the tests");
/* app.spec.js */
const mockery = require('mockery');
var app;
before(() => {
// Mock console
var consoleMock = {
log: () => {}
}
mockery.registerMock('./console', consoleMock);
// Require test module after mocking
app = require('./app');
});
after(() => {
mockery.deregisterAll();
mockery.disable();
});
it('works', () => {});
You could do something along the lines of adding these before/after blocks to your tests, but the issue is that mocha actually uses console.log to print the pretty messages about the results of the test, so you would lose those
describe('Test Name', function() {
var originalLog;
beforeEach(function() {
originalLog = console.log;
console.log = function () {};
});
// test code here
afterEach(function() {
console.log = originalLog;
})
})
The problem is that your output would just look like
Test Name
X passing (Yms)
Without any intermediate text

Producing an error in async.each

I have the following JavaScript:
var async = require('async');
var MyOutputModel = function(persistenceModel, callback) {
async.each(persistenceModel.helpOffers, function(helpOffer, asyncCallback) {
console.log('Original source (loop)');
//Do something ...
asyncCallback();
}, function(err) {
console.log('Original source (done)');
console.log(err);
if(err) return callback(err);
return callback(null, _this);
});
};
module.exports = MyOutputModel;
I would like to test the path containing if(err) return callback(err);...
For doing so I see the only possibility in dependency injection with rewire (https://www.npmjs.com/package/rewire).
In my unit test:
var rewire = require('rewire');
var MyOutputModel = rewire('...');
var AsyncMock = function() {};
AsyncMock.prototype.each = function(array, successCallback, errorCallback) {
console.log('Inside mock!');
callback(new Error());
};
var asyncMock = new AsyncMock();
MyOutputModel.__set__('async', asyncMock); //Rewire for require-dependency-injection
//Act
new CdObjectOutputModel(cdObjectPersistenceModel, function(err, cdObjectOutputModel) {
//Assert
assert(err);
});
However the "mock" doesn't seem to be used. In my unit test I never see 'Inside mock!' but 'Original source (loop)' and 'Original source (done)' on the console. And there is no error.
Anything I do wrong? I've already used rewire for a similar use case and it worked perfectly. Or is there another approach?
This is what rewire does under the hood:
For each rewired path it'll actually read the contents of the file pointed by path which we will call the body
it'll also generate two strings, the first one is called the prelude and it has some helpful vars to avoid overwriting the global ones with __set__, e.g. if we wanted to override the global console as if it were local on our module the contents of the prelude var would be: var console = global.console, this awesome detection of globals which can be overridden locally is done here
The second one is called the appendix and this string contains actually the definitions of the __set__, __get__ and there's also __with__ source, each one is a function defined over module.exports but remember that we're still dealing with strings
Imagine that there's a way to create a fake/test module out of strings! That's what wired does, it concatenates the prelude, the body and the appendix in one big string and it then creates a module with require('module')
e.g. (remember that this is a big string)
// prelude start
var console = global.console
// a lot of globals that can be overriden are defined here :)
// prelude end
// body start
// I'll paste the contents of the rewire's README
var fs = require("fs");
var path = "/somewhere/on/the/disk";
function readSomethingFromFileSystem(cb) {
console.log("Reading from file system ...");
fs.readFile(path, "utf8", cb);
}
exports.readSomethingFromFileSystem = readSomethingFromFileSystem;
// body end
// appendix start
// generation of three functions to set the variables defined above
Object.defineProperty(module.exports, '__set__', {
enumerable: false,
value: function () {
// set contents, we will see this later
}
});
Object.defineProperty(module.exports, '__get__', { ... });
Object.defineProperty(module.exports, '__with__', { ... });
// appendix end
So what rewire does is kind of create a module with this string and return it, and voila you have a magic module with three additional properties, the next step is actually find out what __set__ does, the __set__ source boils down to the following operation:
function (key, value) {
eval(key + ' = ' + value);
}
And that's it so now we know that executing __set__ will just eval whatever key (which for us is any local var) you want, to be set as value but ONLY in this test/fake module.
Now going back to your problem I see that this might be the source of it:
new CdObjectOutputModel(...);
I'm assuming that CdObjectOutputModel is defined perhaps as a subclass of MyOutputModel but remember that in your test var MyOutputModel = rewire('...'); is actually a test/fake module so you're not really overriding the original code of MyOutputModel but overriding kind of a copy of it which exists only locally, I guess that you have to rewire CdObjectOutputModel or perhaps locally make CdObjectOutputModel inherit from the rewired version of MyOutputModel
Just replace callback with errorCallback and it should work fine.

Categories