Reputation: 83
I'm trying to learn about testing promises in NodeJS, and testing methodologies that I've used in other languages are failing me a bit here. The basic question is "how do I effectively test indirect inputs and outputs in one or more chained then (and done or catch) promise blocks?"
Here the source of lib/test.js
:
var Bluebird = require("bluebird"),
fs = Bluebird.promisifyAll(require("fs"));
function read(file) {
return fs.readFileAsync(file)
.then(JSON.parse)
.done(function () {
console.log("Read " + file);
});
}
function main() {
read("test.json");
}
if (require.main === module) {
main();
}
module.exports = read;
And here's the source of tests/test.js
var Bluebird = require("bluebird"),
chai = require("chai"),
expect = chai.expect,
sinon = require("sinon"),
sandbox = sinon.sandbox.create(),
proxyquire = require("proxyquire");
chai.use(require("chai-as-promised"));
chai.use(require("sinon-chai"));
describe("test", function () {
var stub, test;
beforeEach(function () {
stub = {
fs: {
readFile: sandbox.stub()
}
}
test = proxyquire("../lib/test", stub);
});
afterEach(function () {
sandbox.verifyAndRestore();
});
it("reads the file", function () {
test("test.json");
expect(stub.fs.readFile).to.have.been.calledWith("test.json");
});
it("parses the file as JSON", function () {
stub.fs.readFileAsync = sandbox.stub().returns(Bluebird.resolve("foo"));
sandbox.stub(JSON, "parse");
test("test.json");
expect(JSON.parse).to.have.been.calledWith("foo");
});
it("logs which file was read", function () {
stub.fs.readFileAsync = sandbox.stub();
sandbox.stub(JSON, "parse");
test("bar");
expect(console.log).to.have.been.calledWith("Read bar")
});
});
I realize that these examples are trivial and contrived, but I'm looking to try and understand how to test promise chains, not how to read a file and parse it as JSON. :)
Also, I'm not tied to any frameworks or anything like that, so if I inadvertently chose poorly when grabbing any of the included NodeJS libraries, a call-out would be appreciated as well.
Thanks!
Upvotes: 4
Views: 1687
Reputation: 83
@Benjamin Gruenbaum
I ended up going with the following in lib/test.js
:
var Bluebird = require("bluebird"),
fs = Bluebird.promisifyAll(require("fs"));
function read(files) {
return Bluebird.map(files, function (file) {
return fs.readFileAsync(file)
.then(JSON.parse)
.then(function (data) {
console.log("Read " + file);
});
});
}
function main() {
read(["test.json", "test2.json"]);
}
if (require.main === module) {
main();
}
module.exports = read;
And this is tests/test.js
:
var Bluebird = require("bluebird"),
chai = require("chai"),
chaiAsPromised = require("chai-as-promised"),
expect = chai.expect,
sinon = require("sinon"),
sandbox = sinon.sandbox.create(),
proxyquire = require("proxyquire");
chai.use(chaiAsPromised);
chai.use(require("sinon-chai"));
describe("test", function () {
var stub, test;
beforeEach(function () {
stub = {
fs: {
readFile: sandbox.stub()
}
};
sandbox.stub(JSON, "parse");
test = proxyquire("../lib/test", stub);
stub.fs.readFileAsync = sandbox.stub().returns(Bluebird.resolve());
});
afterEach(function () {
sandbox.verifyAndRestore();
});
it("reads the files", function () {
var files = ["test.json", "test2.json"];
return test(files).then(function () {
var expects = files.map(function (file) {
return expect(stub.fs.readFileAsync).to.have.been.calledWith(file);
});
// expects.push(expect(Bluebird.resolve(1)).to.eventually.equal(2));
return Bluebird.all(expects);
});
});
it("parses the files as JSON", function () {
var returns = ["foo", "bar"];
returns.forEach(function (value, index) {
stub.fs.readFileAsync.onCall(index).returns(Bluebird.resolve(value));
});
return test(["baz", "buz"]).then(function () {
var expects = returns.map(function (value) {
return expect(JSON.parse).to.have.been.calledWith(value);
})
// expects.push(expect(Bluebird.resolve(1)).to.eventually.equal(2));
return Bluebird.all(expects);
});
});
it("logs which files were read", function () {
var files = ["bleep", "blorp"];
sandbox.spy(console, "log");
return test(files).then(function () {
var expects = files.map(function (file) {
return expect(console.log).to.have.been.calledWith("Read " + file);
});
// expects.push(expect(Bluebird.resolve(1)).to.eventually.equal(2));
return Bluebird.all(expects);
});
});
});
I left the commented-out 1 === 2 assertions there to ensure I wasn't getting false positives (like when a .then
wouldn't get called because I was doing it wrong).
Upvotes: 1
Reputation: 276596
Assuming that syntax is Mocha, you need to return the promises. After all promises work by return value as you return them from methods, so if you don't return them from the tests the test library can't hook on them.
describe("testing promises with mocha", () => {
it("works by returning promises", () => {
return Promise.resolve("This test passes");
});
it("fails by returning promises", () => {
return Promise.reject(Error("This test fails"));
});
it("Lets you chain promises", () => {
return fs.readFileAsync("test.json").then(JSON.parse);
});
});
(The new function arrow syntax works in NodeJS, if you need to support old node - convert it to function(){
calls)
Upvotes: 2