Skip to content

Commit

Permalink
Fix everything.
Browse files Browse the repository at this point in the history
Adding more tests revealed my approach was completely wrong, so I need an awesome hack instead.
  • Loading branch information
domenic committed Jun 15, 2012
1 parent ea26858 commit d98f2d9
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 23 deletions.
23 changes: 15 additions & 8 deletions mocha-as-promised.js
Expand Up @@ -12,11 +12,17 @@ module.exports = function (mocha) {
} }
duckPunchedAlready = true; duckPunchedAlready = true;


var OriginalRunnable = mocha.Runnable; // Sooooo, this is a huge hack. We want to intercept calls to `Runnable`, but we can't just replace it because it's
mocha.Runnable = function (title, fn) { // a module export which other parts of Mocha use directly. Fortunately, they all use it in the same way:
// `Runnable.call(this, title, fn)`. Thus if we take control of the `.call` method (i.e. shadow the one inherited
// from `Function.prototype`), we have our interception hook.
var callOriginalRunnable = Function.prototype.call.bind(mocha.Runnable);

mocha.Runnable.call = function (thisP, title, fn) {
function newFn(done) { function newFn(done) {
// Run the original `fn`, assuming it's callback-asynchronous (no harm passing `done` if it's not). // Run the original `fn`, passing along `done` for the case in which it's callback-asynchronous. Make sure
var retVal = fn(done); // to forward the `this` context, since you can set variables and stuff on it to share within a suite.
var retVal = fn.call(this, done);


if (isPromise(retVal)) { if (isPromise(retVal)) {
// If we get a promise back... // If we get a promise back...
Expand All @@ -34,13 +40,14 @@ module.exports = function (mocha) {
} }
); );
} else if (fn.length === 0) { } else if (fn.length === 0) {
// If we weren't callback-asynchronous, call `done()` now. If we were then `fn` will call it eventually. // If `fn` is synchronous (i.e. didn't have a `done` parameter and didn't return a promise), call `done`
// now. (If it's callback-asynchronous, `fn` will call `done` eventually since we passed it in above.)
done(); done();
} }
} }


OriginalRunnable.call(this, title, newFn); // Now that we have wrapped `fn` inside our promise-interpreting magic, call the original `mocha.Runnable`
// constructor but give it the magic `newFn` instead of the mundane `fn`.
callOriginalRunnable(thisP, title, newFn);
}; };
mocha.Runnable.prototype = OriginalRunnable.prototype;
mocha.Runnable.prototype.constructor = mocha.Runnable;
}; };
19 changes: 19 additions & 0 deletions test/chai-as-promised.coffee
@@ -0,0 +1,19 @@
"use strict"

Q = require("q")
chai = require("chai")
chaiAsPromised = require("chai-as-promised")

chai.use(chaiAsPromised)

describe "Use with Chai as Promised", =>
it ".should.be.fulfilled", =>
Q.resolve().should.be.fulfilled
it ".should.be.rejected", =>
Q.reject().should.be.rejected
it ".should.be.rejected.with(TypeError, 'boo')", =>
Q.reject(new TypeError("boo!")).should.be.rejected.with(TypeError, "boo")
it ".should.become(5)", =>
Q.resolve(5).should.become(5)
it ".should.eventually.be.above(2)", =>
Q.resolve(5).should.eventually.be.above(2)
20 changes: 20 additions & 0 deletions test/direct.coffee
@@ -0,0 +1,20 @@
"use strict"

Q = require("q")

describe "Direct usage of promise-returning tests", =>
it "should work for a promise fulfilled with no value", =>
Q.resolve()

it "should work for a promise fulfilled with a value", =>
Q.resolve({})

it "should run through an entire promise chain", =>
timeout = setTimeout(
=> throw new Error("The timeout wasn't cleared, so the promise chain must not have run"),
100
)

Q.delay(5).then =>
Q.delay(5).then =>
clearTimeout(timeout)
2 changes: 0 additions & 2 deletions test/setup.js
@@ -1,10 +1,8 @@
var chai = require("chai"); var chai = require("chai");
var chaiAsPromised = require("chai-as-promised");


var mocha = require("mocha"); var mocha = require("mocha");
var mochaAsPromised = require("../mocha-as-promised"); var mochaAsPromised = require("../mocha-as-promised");


chai.use(chaiAsPromised);
chai.should(); chai.should();


mochaAsPromised(mocha); mochaAsPromised(mocha);
23 changes: 10 additions & 13 deletions test/tests.coffee → test/test.coffee
@@ -1,19 +1,16 @@
"use strict" "use strict"


Q = require("q") Q = require("q")
Runnable = require("mocha").Runnable Test = require("mocha").Test


# NOTE: due to an awesome "feature" of Mocha, if you set `this.runnable` in the test context (e.g. in a `beforeEach`), describe "Mocha's `Test` class", ->
# everything breaks. So we use `@test = new Runnable` instead of the more obvious `@runnable = new Runnable`.

describe "Modifications to mocha's `Runnable` constructor", ->
describe "when the function returns a promise fulfilled with no value", -> describe "when the function returns a promise fulfilled with no value", ->
beforeEach -> beforeEach ->
@ran = false @ran = false
testFunc = => testFunc = =>
process.nextTick => @ran = true process.nextTick => @ran = true
return Q.resolve() return Q.resolve()
@test = new Runnable("", testFunc) @test = new Test("", testFunc)


it "should invoke the done callback asynchronously with no argument", (done) -> it "should invoke the done callback asynchronously with no argument", (done) ->
@test.run => @test.run =>
Expand All @@ -26,7 +23,7 @@ describe "Modifications to mocha's `Runnable` constructor", ->
testFunc = => testFunc = =>
process.nextTick => @ran = true process.nextTick => @ran = true
return Q.resolve({}) return Q.resolve({})
@test = new Runnable("", testFunc) @test = new Test("", testFunc)


it "should invoke the done callback asynchronously with no argument", (done) -> it "should invoke the done callback asynchronously with no argument", (done) ->
@test.run => @test.run =>
Expand All @@ -38,7 +35,7 @@ describe "Modifications to mocha's `Runnable` constructor", ->
# Q automatically gives `new Error()` if no rejection reason is supplied, i.e. it is impossible to create # Q automatically gives `new Error()` if no rejection reason is supplied, i.e. it is impossible to create
# a promise rejected with no reason in Q. Create a pseudo-promise manually to test this case. # a promise rejected with no reason in Q. Create a pseudo-promise manually to test this case.
rejected = then: (f, r) => process.nextTick(r) rejected = then: (f, r) => process.nextTick(r)
@test = new Runnable("", => rejected) @test = new Test("", => rejected)


it "should invoke the done callback with a generic `Error`", (done) -> it "should invoke the done callback with a generic `Error`", (done) ->
@test.run (err) => @test.run (err) =>
Expand All @@ -50,7 +47,7 @@ describe "Modifications to mocha's `Runnable` constructor", ->
describe "when the function returns a promise rejected with a reason", -> describe "when the function returns a promise rejected with a reason", ->
beforeEach -> beforeEach ->
@err = new TypeError("boo!") @err = new TypeError("boo!")
@test = new Runnable("", => Q.reject(@err)) @test = new Test("", => Q.reject(@err))


it "should invoke the done callback with that reason", (done) -> it "should invoke the done callback with that reason", (done) ->
@test.run (err) => @test.run (err) =>
Expand All @@ -60,15 +57,15 @@ describe "Modifications to mocha's `Runnable` constructor", ->
describe "when doing normal synchronous tests", -> describe "when doing normal synchronous tests", ->
describe "that succeed", -> describe "that succeed", ->
beforeEach -> beforeEach ->
@test = new Runnable("", =>) @test = new Test("", =>)


it "should succeed normally", (done) -> it "should succeed normally", (done) ->
@test.run(done) @test.run(done)


describe "that fail", -> describe "that fail", ->
beforeEach -> beforeEach ->
@err = new TypeError("boo!") @err = new TypeError("boo!")
@test = new Runnable("", => throw @err) @test = new Test("", => throw @err)


it "should fail normally", (done) -> it "should fail normally", (done) ->
@test.run (err) => @test.run (err) =>
Expand All @@ -78,15 +75,15 @@ describe "Modifications to mocha's `Runnable` constructor", ->
describe "when doing normal asynchronous tests", -> describe "when doing normal asynchronous tests", ->
describe "that succeed", -> describe "that succeed", ->
beforeEach -> beforeEach ->
@test = new Runnable("", (done) => process.nextTick => done()) @test = new Test("", (done) => process.nextTick => done())


it "should succeed normally", (done) -> it "should succeed normally", (done) ->
@test.run(done) @test.run(done)


describe "that fail", -> describe "that fail", ->
beforeEach -> beforeEach ->
@err = new TypeError("boo!") @err = new TypeError("boo!")
@test = new Runnable("", (done) => process.nextTick => done(@err)) @test = new Test("", (done) => process.nextTick => done(@err))


it "should fail normally", (done) -> it "should fail normally", (done) ->
@test.run (err) => @test.run (err) =>
Expand Down

0 comments on commit d98f2d9

Please sign in to comment.