Permalink
Browse files

Fix everything.

Adding more tests revealed my approach was completely wrong, so I need an awesome hack instead.
  • Loading branch information...
1 parent ea26858 commit d98f2d95197896cd7b948b6208cb6c1235f43eed @domenic committed Jun 15, 2012
Showing with 64 additions and 23 deletions.
  1. +15 −8 mocha-as-promised.js
  2. +19 −0 test/chai-as-promised.coffee
  3. +20 −0 test/direct.coffee
  4. +0 −2 test/setup.js
  5. +10 −13 test/{tests.coffee → test.coffee}
View
@@ -12,11 +12,17 @@ module.exports = function (mocha) {
}
duckPunchedAlready = true;
- var OriginalRunnable = mocha.Runnable;
- mocha.Runnable = function (title, fn) {
+ // Sooooo, this is a huge hack. We want to intercept calls to `Runnable`, but we can't just replace it because it's
+ // 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) {
- // Run the original `fn`, assuming it's callback-asynchronous (no harm passing `done` if it's not).
- var retVal = fn(done);
+ // Run the original `fn`, passing along `done` for the case in which it's callback-asynchronous. Make sure
+ // 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 we get a promise back...
@@ -34,13 +40,14 @@ module.exports = function (mocha) {
}
);
} 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();
}
}
- 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;
};
@@ -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)
View
@@ -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)
View
@@ -1,10 +1,8 @@
var chai = require("chai");
-var chaiAsPromised = require("chai-as-promised");
var mocha = require("mocha");
var mochaAsPromised = require("../mocha-as-promised");
-chai.use(chaiAsPromised);
chai.should();
mochaAsPromised(mocha);
@@ -1,19 +1,16 @@
"use strict"
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`),
-# everything breaks. So we use `@test = new Runnable` instead of the more obvious `@runnable = new Runnable`.
-
-describe "Modifications to mocha's `Runnable` constructor", ->
+describe "Mocha's `Test` class", ->
describe "when the function returns a promise fulfilled with no value", ->
beforeEach ->
@ran = false
testFunc = =>
process.nextTick => @ran = true
return Q.resolve()
- @test = new Runnable("", testFunc)
+ @test = new Test("", testFunc)
it "should invoke the done callback asynchronously with no argument", (done) ->
@test.run =>
@@ -26,7 +23,7 @@ describe "Modifications to mocha's `Runnable` constructor", ->
testFunc = =>
process.nextTick => @ran = true
return Q.resolve({})
- @test = new Runnable("", testFunc)
+ @test = new Test("", testFunc)
it "should invoke the done callback asynchronously with no argument", (done) ->
@test.run =>
@@ -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
# a promise rejected with no reason in Q. Create a pseudo-promise manually to test this case.
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) ->
@test.run (err) =>
@@ -50,7 +47,7 @@ describe "Modifications to mocha's `Runnable` constructor", ->
describe "when the function returns a promise rejected with a reason", ->
beforeEach ->
@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) ->
@test.run (err) =>
@@ -60,15 +57,15 @@ describe "Modifications to mocha's `Runnable` constructor", ->
describe "when doing normal synchronous tests", ->
describe "that succeed", ->
beforeEach ->
- @test = new Runnable("", =>)
+ @test = new Test("", =>)
it "should succeed normally", (done) ->
@test.run(done)
describe "that fail", ->
beforeEach ->
@err = new TypeError("boo!")
- @test = new Runnable("", => throw @err)
+ @test = new Test("", => throw @err)
it "should fail normally", (done) ->
@test.run (err) =>
@@ -78,15 +75,15 @@ describe "Modifications to mocha's `Runnable` constructor", ->
describe "when doing normal asynchronous tests", ->
describe "that succeed", ->
beforeEach ->
- @test = new Runnable("", (done) => process.nextTick => done())
+ @test = new Test("", (done) => process.nextTick => done())
it "should succeed normally", (done) ->
@test.run(done)
describe "that fail", ->
beforeEach ->
@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) ->
@test.run (err) =>

0 comments on commit d98f2d9

Please sign in to comment.