I'm working to understand unit testing in JavaScript, using Mocha/Sinon/Chai. I have seen the function done() used. But I cannot seem to find documentation for this function. It does not seem to be a part of the JavaScript language. If it were, I would expect to see it in the Mozilla documentation under [something].prototype.done(). But it's not there. I don't see it under jQuery's documentation, nor under Mocha's.
On another thread, I see this example of done():
it('should have data.', function () {
db.put(collection, key, json_payload)
.then(function (result) {
result.should.exist;
done();
})
.fail(function (err) {
err.should.not.exist;
done();
})
})
What is done(), what language or tooling is it a part of, and where is the documentation for it? Is done() just a naming convention for a callback function?
Done is a callback that mocha will provided as the first parameter to a unit testing it block. It is usually needed when testing asynchronous code, as it can be call to notify mocha that the it block is completed. It is good practice to name the callback done. However, you can name it as you want.
You can find its documentation here just hit ctrl + f on windows or ⌘ + f on MAC, then enter done.
it('should have data.', function (done) { // inject done here
db.put(collection, key, json_payload)
.then(function (result) {
result.should.exist;
done();
})
.fail(function (err) {
err.should.not.exist;
done();
})
})
Copied the following from mocha website.
Testing asynchronous code with Mocha could not be simpler! Simply invoke the callback when your test is complete. By adding a callback (usually named done) to it(), Mocha will know that it should wait for this function to be called to complete the test. This callback accepts both an Error instance (or subclass thereof) or a falsy value; anything else will cause a failed test
Related
I am dealing with a code mixing node-style callbacks and Bluebird promises, and I need to write some unit tests for it.
In particular, cache.js exposes the init() function, which works with promises. It is then called by the doSomething() function in another file (e.g. index.js) which in turn accepts a callback that has to be invoked at the end of init().
Pseudocode is as follows:
// [ cache.js ]
function init() {
return performInitialisation()
.then((result) => return result);
}
// [ index.js ]
var cache = require('./cache');
function doSomething(callback) {
console.log('Enter');
cache.init()
.then(() => {
console.log('Invoking callback');
callback(null);
})
.catch((err) => {
console.log('Invoking callback with error');
callback(err);
});
console.log('Exit');
}
A possible unit test could be (showing only relevant code):
// [ index.test.js ]
...
var mockCache = sinon.mock(cache);
...
it('calls the callback on success', function(done) {
mockCache.expects('init')
.resolves({});
var callback = sinon.spy();
doSomething(callback);
expect(callback).to.have.been.calledOnce;
done();
});
This test passes, however changing the expectation to not.have.been.calledOnce also passes, which is wrong.
Also, console logs are out of sequence:
Enter
Exit
Invoking callback
I have looked at several possibilities, none of which worked:
Using chai-as-promised, e.g. expect(callback).to.eventually.have.been.calledOnce;
Refactoring doSomething() to be simply:
function doSomething(callback) {
cache.init()
.asCallback(callback);
}
Can anyone help me understand what I am doing wrong and how I can fix it please?
console logs are out of sequence
The logs are in the correct order because your Promise will be async meaning, at the very least, the internal console logs calls in then & catch will run on the next tick.
As to why the test is failing is the result of a couple of issues, first one is you don't appear to have sinon-chai configured correctly, or at best your calledOnce assertion isn't kicking in. Just to confirm, the top of your test file should something like:
const chai = require("chai");
const sinonChai = require("sinon-chai");
chai.use(sinonChai);
If you have that and it's still not working correctly then might be worth opening an issue on the sinon-chai lib, however, a simple workaround is to switch to sinon assertions e.g.
sinon.assert.calledOnce(callback)
Secondly, when you do eventually fix this, you'll probably find that the test will now fail...everytime. Reason being you've got the same problem in your test that you have with your logging - your asserting before the internal promise has had a chance to resolve. Simplest way of fixing this is actually using your done handler from Mocha as your assertion
mockCache.expects('init').resolves({});
doSomething(() => done());
In other words, if done get's called then you know the callback has been called :)
Following James' comment I revisited my tests like this:
it('calls the callback on success', function(done) {
mockCache.expects('init')
.resolves({});
doSomething(done);
});
it('calls the callback on error', function(done) {
mockCache.expects('init')
.rejects('Error');
doSomething((err) => {
if (err === 'Error') {
done();
} else {
done(err);
}
});
});
I'm looking to create some jasmine specs for a piece of code that has async functionality.
In the jasmine docs it shows the example:
it("takes a long time", function(done) {
setTimeout(function() {
done();
}, 9000);
});
Using the done function and a setTimeout, my issue with this is setTimout could be fragile i.e. delays in test runs in enviros
is there an alternative solution to such tests where I don't have to use a timeout?
Thanks in advance
In this example setTimeout is actually the function being tested. It's used as a representative example of an asynchronous function. The key point is that you must explicitly call done() when your test is complete. Your code should look something like:
it("takes a long time", function(done) {
myMethod('foo', 'bar', function callback() {
assert(...)
done();
}); // callback-style
}
it("takes a long time", function(done) {
myMethod('foo', 'bar').then(function() {
assert(...)
done();
}); // promise-style
});
it("takes a long time", async function(done) {
await myMethod('foo', 'bar')
assert(...)
done()
});
The documented function is intended to illustrate using the done callback following a long-running method, and should not be used for actual tests.
Normally, you would expect a long running function to be supplied with a callback in which you would call the done function. For example, you could write a unit test involving a file that took a long time to write data:
it("writes a lot of data", function(done) {
var fd = 999; // Obtain a file descriptor in some way...
fs.write(fd, veryLongString, function (err, written, string) {
// Carry out verification here, after the file has been written
done();
});
Again, this is only illustrative, as you would generally not want to write to a file within the body of a unit test. But the idea is that you can call done after some long-running operation.
I want to test some promises with jasmine node. However, the test runs but it says that there are 0 assertions. This is my code, is there something wrong? The then part is successfully called, so if I have a console.log there, it gets called. If I have the code test a http request, on the success, the assertion is correctly interpretated.
describe('Unit tests', function () {
it("contains spec with an expectation", function() {
service.getAllClients().then(function (res) {
expect("hello world").toEqual("hello world");
done();
}).catch(function (err) {
fail();
});
});
});
You need to specify the done argument to the callback you pass to it, so Jasmine knows you are testing something asynchronously:
it("contains spec with an expectation", function(done) {
// ...
When you include that parameter, Jasmine will wait for a while for you to call done so it knows when you're done.
done();
Secondly, in an asynchronous test, it probably is better to fail with a call of done.fail:
done.fail();
I'm very new to unit tests, mocha, and should.js, and I'm trying to write a test for an asynchronous method that returns a promise. Here is my test code:
var should = require("should"),
tideRetriever = require("../tide-retriever"),
moment = require("moment"),
timeFormat = "YYYY-MM-DD-HH:mm:ss",
from = moment("2013-03-06T00:00:00", timeFormat),
to = moment("2013-03-12T23:59:00", timeFormat),
expectedCount = 300;
describe("tide retriever", function() {
it("should retrieve and parse tide CSV data", function() {
tideRetriever.get(from, to).then(
function(entries) { // resolve
entries.should.be.instanceof(Array).and.have.lengthOf(expectedCount);
},
function(err) { // reject
should.fail("Promise rejected", err);
}
);
});
});
When I manually test the tideRetriever.get method, it consistently resolves an array of 27 elements (as expected), but the test will not fail regardless of the value of expectedCount. Here is my simple manual test:
tideRetriever.get(from, to).then(
function(entries) {
console.log(entries, entries.length);
},
function(err) {
console.log("Promise rejected", err);
}
);
I can also post the source for the module being tested if it's necessary.
Am I misunderstanding something about Mocha or should.js? Any help would be greatly appreciated.
UPDATE
At some point Mocha started to support returning Promise from test instead of adding done() callbacks. Original answer still works, but test looks much cleaner with this approach:
it("should retrieve and parse tide CSV data", function() {
return tideRetriever.get(from, to).then(
function(entries) {
entries.should.be.instanceof(Array).and.have.lengthOf(expectedCount);
}
);
});
Check out this gist for complete example.
ORIGINAL
CAUTION. Accepted answer works only with normal asynchronous code, not with Promises (which author uses).
Difference is that exceptions thrown from Promise callbacks can't be caught by application (in our case Mocha) and therefore test will fail by timeout and not by an actual assertion. The assertion can be logged or not depending on Promise implementation. See more information about this at when documentation.
To properly handle this with Promises you should pass err object to the done() callback instead of throwing it. You can do it by using Promise.catch() method (not in onRejection() callback of Promise.then(), because it doesn't catch exceptions from onFulfilment() callback of the same method). See example below:
describe("tide retriever", function() {
it("should retrieve and parse tide CSV data", function(done) {
tideRetriever.get(from, to).then(
function(entries) { // resolve
entries.should.be.instanceof(Array).and.have.lengthOf(expectedCount);
done(); // test passes
},
function(err) { // reject
done(err); // Promise rejected
}
).catch(function (err) {
done(err); // should throwed assertion
});
});
});
PS done() callback is used in three places to cover all possible cases. However onRejection() callback can be completely removed if you don't need any special logic inside it. Promise.catch() will handle rejections also in this case.
When testing asynchronous code, you need to tell Mocha when the test is complete (regardless of whether it passed or failed). This is done by specifying an argument to the test function, which Mocha populates with a done function. So your code might look like this:
describe("tide retriever", function() {
it("should retrieve and parse tide CSV data", function(done) {
tideRetriever.get(from, to).then(
function(entries) { // resolve
entries.should.be.instanceof(Array).and.have.lengthOf(expectedCount);
done();
},
function(err) { // reject
should.fail("Promise rejected", err);
done();
}
);
});
});
Note that the way Mocha knows this is an async test and it needs to wait until done() is called is just by specifying that argument.
Also, if your promise has a "completed" handler, which fires both on success and failure, you can alternatively call done() in that, thus saving a call.
More info at:
http://mochajs.github.io/mocha/#asynchronous-code
Mocha website states:
"To make things even easier, the done() callback accepts an error, so we may use this directly: [see their example]"
So lets try that:
it('works',function(done){
expect(1).to.be(1)
done( new Error('expected error') )
})
/* Insert the error manually for testing and clarity. */
run it and:
1 failing
1) works:
Error: expected error
at Context.<anonymous>
[stack trace]
How do we make the test pass when the error response is the desired result?
That's not async. The callback function is just there for you to inform mocha that you're testing async code and so mocha shouldn't run the next test until you call the callback function. This is an example of how to use the callback function:
it('works',function(done){
setTimeout(function(){
// happens 0.5 seconds later:
expect(1).to.be(1);
done(); // this tells mocha to run the next test
},500);
});
Also, mocha does not handle any form of exceptions, async or otherwise. It leaves that up to an exception library of your choosing. In your case you're using expect.js? If so, expect handles expected errors via the throwException method (also called throwError):
it('throws an error',function(done){
expect(function(){
throw new Error('expected error');
}).to.throwError(/expected error/);
});
Now, in general async code in node.js don't throw errors. They pass errors to the callback as parameters instead. So to handle async errors you can simply check the err object:
// using readFile as an example of async code to be tested:
it('returns an error',function(done){
fs.readFile(filename, function (err, data) {
expect(err).to.be.an(Error);
done(); // tell mocha to run next test
})
});
So use to.throwError() if you're checking synchronous errors and to.be.an(Error) if you're checking async errors.
Additional notes:
The first time I saw this I was stumped. How can mocha know that the test is synchronous or asynchronous when the only difference is weather the function you pass to it accepts an argument or not? In case you're like me and are scratching your head wondering how, I learned that all functions in javascript have a length property that describes how many arguments it accepts in its declaration. No, not the arguments.length thing, the function's own length. For example:
function howManyArguments (fn) {
console.log(fn.length);
}
function a () {}
function b (x) {}
function c (x,y,z) {}
howManyArguments(a); // logs 0
howManyArguments(b); // logs 1
howManyArguments(c); // logs 3
howManyArguments(howManyArguments); // logs 1
howManyArguments(function(x,y){}); // logs 2
So mocha basically checks the function's length to determine weather to treat it as a synchronous function or asynchronous function and pauses execution waiting for the done() callback if it's asynchronous.
Even more additional notes:
Mocha, like most other js unit test runners and libraries, work by catching errors. So it expects functions like expect(foo).to.be.an.integer() to throw an error if the assertion fails. That's how mocha communicates with assertion libraries like expect or chai.
Now, as I mentioned above, a common idiom in node is that async functions don't throw errors but passes an error object as the first argument. When this happens mocha cannot detect the error and so can't detect a failing test. The work-around to this is that if you pass the error object from the async code to the callback function it will treat it the same as a thrown error.
So, taking one of my examples above:
it('executes without errors',function(done){
fs.readFile(filename, function (err, data) {
done(err); // if err is undefined or null mocha will treat
// it as a pass but if err is an error object
// mocha treats it as a fail.
})
});
Or simply:
it('executes without errors',function(done){
fs.readFile(filename,done);
});
Strictly speaking, this feature is a bit redundant when used with libraries like expect.js which allows you to manually check the returned error object but it's useful for when your assertion library can't check the error object (or when you don't really care about the result of the async function but just want to know that no errors are thrown).
You can also return your async such as promise as below.
it('Test DNA', () => {
return resolvedPromise.then( (result)=>{
expect(result).to.equal('He is not a your father.');
},(err)=>{
console.log(err);
});
});