I am trying to write unit tests for a function that reads a jsonfile into an object. I read the file with
jsonfile.readFile(filename, function (err, obj) {
//...
});
For my unit tests, I want to mock this function so that, rather than actually reading the file, it will simply return a fixed json block and pass it into the callback.
What I'm having trouble with is how to mock the function. I've seen sinon, which says it supports mocking functions, but I can't find anything that describes how to actually define custom behavior for the function I'm mocking. Sinon looks like it allows me to define what I want the function to return, how often I expect it to be called, etc, but not actually define a mocked function.
Basically, I want something like this:
mock(jsonfile, 'readFile', function(filename, callback) {
callback(null, {attr1:"foo"});
});
How do I do this with sinon?
But actually, why don't you just replace readFile by a function with the same definition (so that it doesn't break the code using it). And just return your mock data.
jsonfile.readFile = function(filePath, callback) {
callback(null, { mockData: "foo" });
};
easy as that.
Otherwise, you can use a Proxy if you don't want to deal with the definition :
const jsonfile = {
readFile: function(filename, callback) {
callback();
}
};
// intercept every call to readFile and always return the mock data
jsonfile.readFile = new Proxy(jsonfile.readFile, {
apply: function(target, thisArg, args) {
return args[1](null, { someMocking: "" });
}
});
// call readFile as usual
jsonfile.readFile('testfile', function(err, result) {
console.log(result);
});
Proxies work as interceptors.
https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Proxy
This is not straightforward in testing because it involved callbacks. You need to test wether a callback you passed to readFile was called with right arguments, which in this case is the dummyFile.
import sinon from 'sinon'
import jsonfile from './jsonfile'
const jsonFileMock = sinon.spy(jsonfile, 'readFile');
const callback = sinon.spy();
jsonfile.readFile(callback);
jsonFileMock.callsArgWith(1, 'dummyFileName');
expect(callback.calledWith('dummyFileName')).to.be.true;
jsonFileMock.restore();
If you want to abstract this into a function, than it can be something like :
function mock(module, method, ...callbacks){
const stub = sinon.stub(jsonfile, 'readFile');
callbacks.forEach((callback, index) => {
stub.callsArgWith(index, callback);
});
}
The function I was looking for is stub.callsFake():
> Thing = {
... meth : function() { console.log(1) }
... }
> Thing.meth()
1
> var stub = sinon.stub(Thing, 'meth')
> stub.callsFake(function() { console.log(2) })
> Thing.meth()
2
> stub.restore()
> Thing.meth()
1
It doesn't look like mock is capable of what I want to do.
Related
I have a function in my project which calls a function from bootstrapper object of window. Below is the function:
export default function measurement(analObj) {
if (window.Bootsrapper._trackAnalytics === function) {
window.Bootstrapper._trackAnalytics(analObj);
}
}
I wrote below code to unit test this function in jest:
import measurement from "../helpers/measurement";
describe('Test measurement', () => {
beforeAll(() => {
const Bootstrapper = {
_trackAnalytics: function(obj) {
return obj;
},
};
window.Bootstrapper = Bootstrapper;
})
test('should send analytics object to rtrack analyitics', () => {
const testObj = {
pageName: "Leave Abasence"
}
const result = measurement(testObj);
expect(testObj).toEqual(result);
})
})
I get "undefined" for result variable that comes from measurement function call as I am unable to make window.measurement._trackAnalytics function available for measurement function at run time.
I would like to know:
Is my approach correct to unit test this scenario? If Yes, How to make the _trackAnalytics function available for measurement function while unit test run time.
Please suggest any other better approach if you know.
The window.measurement._trackAnalytics function is indeed available for measurement function when your test runs. Otherwise, you would get a TypeError for calling something that is not a function.
The problem is that in the measurement method there is nothing being returned. The _trackAnalytics method is called but its result is not returned. That's why you get undefined as a result.
In order to check that it is indeed being called I would use a jest mock function. The test would look like:
test('should send analytics object to rtrack analyitics', () => {
const testObj = {
pageName: 'Leave Abasence'
};
measurement(testObj);
expect(window.Bootstrapper._trackAnalytics).toHaveBeenCalledTimes(1);
expect(window.Bootstrapper._trackAnalytics).toHaveBeenCalledWith(testObj);
});
Note that your code has some problems (which I expect are typos). In the if condition you are checking Bootsrapper instead of Bootstrapper. And you are checking if it is equal to function instead of checking with typeof. I think the line should look:
if (typeof window.Bootstrapper._trackAnalytics === 'function') {
I have code like this in my describe: before(a).
When a looks like this:
function a() {
return chai.request(app)
...
.then(res => {
res.blah.should.blah;
return Promise.resolve();
});
}
...everything works great (it's a very quick call.)
However, when I make a take some input variables:
function a(dummy, my_var) {
return chai.request(app)
... // use my_var here
.then(res => {
res.blah.should.blah;
console.log("finished!");
return Promise.resolve();
});
}
The promise never resolves. Two observations:
finished! does get properly output (immediately)
dummy is populated.
It's populated with this:
function (err) {
if (err instanceof Error || toString.call(err) === '[object Error]') {
return done(err);
}
if (err) {
if (Object.prototype.toString.call(err) === '[object Object]') {
return done(new Error('done() invoked with non-Error: ' + JSON.stringify(err)));
}
return done(new Error('done() invoked with non-Error: ' + err));
}
if (result && utils.isPromise(result)) {
return done(new Error('Resolution method is overspecified. Specify a callback *or* return a Promise; not both.'));
}
done();
}
I don't know where that's from exactly, but I don't need it, that's why I just put it into a dummy variable.
Now I suspect that has something to do with it, but I wonder why my workaround doesn't do the trick here.
I still want to use before(a) for the default case, leaving my_var undefined. When I do actually want to pass that variable, I intended to:
before(() => {
return a('my_content');
})
That's because Mocha inspects the function you pass to before to check how many parameters are defined on it like this:
this.async = fn && fn.length;
If there's any parameter defined, then the function is deemed to be asynchronous. (Mocha also checks if it returns a promise but that's a different test.) If there's at least one parameter defined, then Mocha passes as 1st parameters a function that you are meant to call when your before callback is done doing its work. That callback (traditionally named done) is useful for code that does not use promises.
If you do not call it, then Mocha waits forever even if you return a promise.
Note that Mocha does the same with all other hooks: beforeEach, after, etc., and with the callback you pass to it.
Mocha before, after, it function accepts one parameter - callback. It should be defined and called when your test uses async functions:
db.connect is async function:
before(done => {
db.connect(done);
}
When db.connect returns a promise you shouldn't use callback and could return the promise:
before(() => {
return db.connect();
}
In your code, you call before with a function, which accepts two parameters. Mocha interprets the first parameter as callback, and tries to call it.
To prevent this issue, you need to call a function directly and pass params in it:
before(() => {
return a(dummy, my_var);
}
I am confused on this one because every tutorial I have found so far assumes I can edit the library code, or that the library only has call backs or the call back as the last parameter.
The library I am using has every function set up like
function(successCallBack(result), FailCallBack(error),options)
So in each instance, I end up using code like
var options={stuff1:1, stuff2:2};
doStuff(success,failure,options);
function success(result){
//handle, usually with another call to a similar function, chaining callbacks together
};
function failure(error){
//handle error
};
How do I convert these into promises when I only have control of the call, and the success and failures?
Also, as a bonus, the chains are accessing variables outside them.
var options={stuff1:1, stuff2:2};
doStuff(success,failure,options);
function success(result){
var options2={stuff1:1, stuff2:2};
doStuff2(function(result2){
processStuff(result1, result2);
},function(error){
//handle error
},options2)
};
function failure(error){
//handle error
};
function processSuff(result1,result2){
//do things to results
}
Thanks
You could use the following function. It accepts a function to promisify and options, and returns promise:
let promisify = (fn, opts) => {
return new Promise((resolve, reject) => {
fn(resolve, reject, opts);
});
}
It could be used as follows:
promisify(doStuff, options)
.then(data => console.log('Success call', data))
.catch(err => console.log('Error', err));
Rather than call promisify every time you want to use your function, you can wrap it once and get a new function that you can just use as needed. The new function will return a promise and resolve/reject instead of the success and fail callbacks. This particular version of promisify is specifically coded for the calling convention you show of fn(successCallback, errorCallback, options). If you have other functions with different calling conventions, then you can create a different promisify version for them:
// return a promisified function replacement for this specific
// calling convention: fn(successCallback, errorCallback, options)
function promisify1(fn) {
return function(options) {
return new Promise((resolve, reject) => {
fn(resolve, reject, options);
});
}
}
So, then you would use this like this:
// do this once, somewhere near where doStuff is imported
let doStuffPromise = promisify1(doStuff);
// then, anytime you would normally use doStuff(), you can just use
// doStuffPromise() instead
doStuffPromise(options).then(results => {
// process results here
}).catch(err => {
// handle error here
});
The advantage of doing it this way is that you can "promisify" a given function of set of functions just once in your project and then just use the new promisified versions.
Suppose you imported an object that had a whole bunch of these types of interfaces on it. You could then make yourself a promisifyObj() function that would make a promisified interface for all the functions on the object.
// a "Promise" version of every method on this object
function promisifyAll(obj) {
let props = Object.keys(obj);
props.forEach(propName => {
// only do this for properties that are functions
let fn = obj[propName];
if (typeof fn === "function") {
obj[propName + "Promise"] = promisify1(fn);
}
});
}
So, if you had an object named myModule that had all these methods with this non-promise interface on it, you could promisify that module in one step:
promisifyAll(myModule);
And, then you could use any method from that object by just adding the "Promise" suffix to the method name:
myModule.someMethodPromise(options).then(results => {
// process results here
}).catch(err => {
// handle error here
});
I set up a callback function inside my Meteor async method to be called on "readable" event. But the callback is not being called when the on."readable" is being fired (I know it's being fired from the console.log I set up).
Am I missing something here? I've been at it for a few hours now trying a few different things!
Meteor.startup(() => {
Meteor.call("getfeed", function(feedloader) {
//I get: TypeError: undefined is not a function]
console.log(feedloader);
});
});
Meteor.methods({
getfeed: function(callb) {
var req = request('http://feeds.feedburner.com/Techcrunch');
var feedparser = new FeedParser();
testing = [];
//........a bunch of functions........
feedparser.on('readable', function() {
var stream = this
, meta = this.meta
, item;
while (item = stream.read())
{
//I'm pushing the results into testing var
testing.push(item);
}
//From the logs I can see that this is called 12 times
//but the callback's not firing!!!
console.log(testing.length);
callb(testing);
});
}
});
Meteor methods are not asynchronous functions in the sense that they do not get the callback argument even though you pass it when you "call" a method. Instead each method is executed within a Fiber which is another flavor of dealing with asynchronous code.
Fortunately, Meteor has a nice helper that allows you to mix both styles. What you need to do is wrap the "pure" asynchronous part of your method code with Meteor.wrapAsync. This structure should look more or less like this:
Meteor.methods({
getfeed: function() {
var wrapped = Meteor.wrapAsync(function (callb) {
var feedparser = new FeedParser();
testing = [];
// ...
feedparser.on('readable', function() {
// probably the same code you have, but without "callb()"
});
feedparser.on('end', function () {
// NOTE: No error here, so the first argument must be null.
callb(null, testing);
})
});
// NOTE: Finally, call the wrapped function
return wrapped();
}
});
I am trying to write a test using AVA but I can't seem to get it to work write. fn passes the callback function through all of my functions and calls it once it's done everything. My test is
import test from 'ava';
import fn from './index.js';
test('NonLiteral && Literal', function (t) {
fn('test.txt', '', function (res) {
console.log(res);
t.is(res, '');
});
});
The res is
This is a test
How is it going
So far!!!
but it is saying that my test is passing. I've been following this test. Here is the snippet I've been looking at
test('throwing a named function will report the to the console', function (t) {
execCli('fixture/throw-named-function.js', function (err, stdout, stderr) {
t.ok(err);
t.match(stderr, /\[Function: fooFn]/);
// TODO(jamestalmage)
// t.ok(/1 uncaught exception[^s]/.test(stdout));
t.end();
});
});
Can someone explain to me what I am doing wrong?
Sorry for the confusion, unfortunately that unit test you are looking at uses tap, not AVA. (AVA does not use itself for testing ... yet).
I am guessing that fn is async. In that case, you probably want to use test.cb.
test.cb('NonLiteral && Literal', function (t) {
fn('test.txt', '', function (res) {
console.log(res);
t.is(res, '');
t.end();
});
});
Now, it looks like maybe fn will call that callback more than once, but it is an error to call t.end() more than once. If that is so, you will need to do something like this:
test.cb('NonLiteral && Literal', function (t) {
var expected = ['foo', 'bar', 'baz'];
var i = 0;
fn('test.txt', '', function (res) {
t.is(res, expected[i]);
i++;
if (i >= expected.length) {
t.end();
}
});
});
Finally, I would encourage you to consider implementing a Promise based api so you can take advantage of async functions and the await keyword. It ends up creating much cleaner code than callbacks. In situations where you want to call a callback multiple times, consider Observables. Testing strategies for both are documented in the AVA docs. Further info on Observables is pretty easy to find by Googling.
Thanks for trying AVA. Keep those questions coming!