Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

should.js not causing mocha test to 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.

like image 561
SimpleJ Avatar asked Jun 05 '14 22:06

SimpleJ


2 Answers

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.

like image 115
Yaroslav Admin Avatar answered Oct 19 '22 23:10

Yaroslav Admin


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

like image 39
coudy Avatar answered Oct 19 '22 23:10

coudy