Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Add support for promise-returning tests #202

Closed
wants to merge 1 commit into from

8 participants

Kris Kowal Domenic Denicola Rajan Agaskar Stepan Riha Logan Smyth Davis W. Frank Felix Jendrusch Gregg Van Hove
Kris Kowal

This patch makes it possible for it blocks to return promises for their results, obviating the need for waitsFor blocks. Particularly, this supports jQuery, Dojo, and Q style promises.

var Q = require("q");
describe("Q.delay", function () {
    it("can time out", function () {
        return Q.delay(1000).timeout(500)
        .then(function (value) { // fulfillment callback
            expect(true).toBe(false); // should not get here
        }, function (error) {
            expect(error).toBe("Timed out");
        });
    });
});
Domenic Denicola

Notably, with appropriate plugins to assertion libraries (coming soon for Chai!), you can get stuff like

var Q = require("q");
describe("Q.delay", function () {
    it("can time out", function () {
        return Q.delay(1000).timeout(500).should.be.rejectedWith("Timed out");
    });
});
Rajan Agaskar
Owner

The idea seems sound, but this commit needs to add some tests before we'd be able to merge it.

Thanks!

Kris Kowal

Awesome. I’ll make this happen.

Kris Kowal

@ragaskar Tests included. Thanks!

Kris Kowal

@domenic I bet we could obviate the need for the return. Consider, in Jasmine’s parlance:

expect(aPromise).toBeFulfilled().toEqual(10);
expect(bPromise).toBeRejectedWith("Timed out");

Or some such equivalent sugar. The trick is for toBeFulfilled or toBeRejected or toBeResolved to enqueue a task to the current spec that waits for the resolution of the promise before completing.

Stepan Riha

@kriskowal, the return is necessary, since it'll allow the asserts to be independent of the runtime environment. IMO, you don't want to couple Chai's implementation with Jasmine's. I want to be able to use the same asserts in Mocha (using @domenic's fork [https://github.com/visionmedia/mocha/pull/329/commits]).

Domenic Denicola

BTW a progress update on my as-yet-unpublished Chai plugin: so far I have

promise.should.be.fulfilled
promise.should.be.rejected
promise.should.be.rejected.with(TypeError)
promise.should.be.rejected.with(TypeError, "message substring")
promise.should.be.rejected.with("message substring")
promise.should.be.rejected.with(TypeError, /message matcher/)
promise.should.be.rejected.with(/message matcher/)

as well as negated versions of all these (promise.should.not.be.fulfilled etc.)

The next big thing will be adding promise.should.eventually.eql(5), promise.should.eventually.contain(6), etc., i.e. adding an eventually extender that allows you to use normal Chai assertions on the promise's fulfillment value.

Stepan Riha

@domenic, what are the assert equivalents going to be called? Simply fulfilled, isFullfiled, getsFulfilled or something else?

I'm asking because I'd like to use the same signature in our asserts so that we can drop in your implementation once it's public.

Also, in the assert style, it's useful for the /message matcher/ to optionally take a callback in which you could then do the eventual asserts.

Domenic Denicola

Announcing: Chai as Promised!

@nonplus I haven't given that much thought, as I don't use the assert style myself. But I'd be happy to add it! Perhaps we should take this discussion to the Chai mailing list.

Kris Kowal

@ragaskar I’ve added tests. Please let me know if the pull needs further refinement before you can accept it.

Logan Smyth

Hey all, I was wondering what the status was on this. Are you guys just busy or are there things that need to be addressed. This would be an excellent addition I think. I'm happy to help.

Rajan Agaskar
Owner

Our main jasmine-core focus right now is on a pretty big factor that should make it easier for people to contribute to and extend jasmine. Happily, one of these things is a better approach to async -- while it doesn't look quite like the syntax proposed here, I think you'll be quite happy with it. If you want a sneak peek you can look at the 2_0 branch (https://github.com/pivotal/jasmine/commits/2_0, specifically this commit: a526ebf).

Thanks!

Logan Smyth

Cool, thanks for replying so quickly! I'll take a look.

Aligned logankd referenced this pull request in velesin/jasmine-jquery
Closed

Support jQuery Promises #121

Davis W. Frank
Owner

Are people looking at master? Does the Mocha-inspired async syntax work for you?

Felix Jendrusch

I'm using this for now.

Gregg Van Hove
Owner

In 2.0, its can receive a done callback to invoke when they have completed asynchronous tests, which should solve this issue. Closing for now, please take a look and if this doesn't cover your use case, please re-open or create a new pull on top of master.

Gregg Van Hove slackersoft closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 21, 2012
  1. Kris Kowal
This page is out of date. Refresh to see the latest.
Showing with 147 additions and 5 deletions.
  1. +100 −0 spec/core/SpecRunningSpec.js
  2. +47 −5 src/core/Block.js
100 spec/core/SpecRunningSpec.js
View
@@ -229,6 +229,106 @@ describe("jasmine spec running", function () {
expect(yet_another_spec.results().getItems()[0].passed()).toEqual(false);
});
+ it("should run asynchronus tests that return fulfilling promises", function () {
+ var done, spec;
+ runs(function () {
+ env.describe('promise test', function () {
+ spec = env.it('should resolve', function () {
+ return {
+ then: function (resolve, reject) {
+ setTimeout(function() {
+ resolve();
+ done = true;
+ }, 0);
+ }
+ }
+ });
+ });
+ env.execute();
+ });
+ waitsFor(function () {
+ return done;
+ });
+ runs(function () {
+ expect(spec.resolved).toBe(true);
+ expect(spec.results().getItems().length).toEqual(0);
+ });
+ });
+
+ it("should run asynchronus tests that return rejecting promises", function () {
+ var done, spec;
+ runs(function () {
+ env.describe('promise test', function () {
+ spec = env.it('should reject', function () {
+ return {
+ then: function (resolve, reject) {
+ setTimeout(function() {
+ reject(new Error("I should not be."));
+ done = true;
+ }, 0);
+ }
+ }
+ });
+ });
+ env.execute();
+ });
+ waitsFor(function () {
+ return done;
+ });
+ runs(function () {
+ expect(spec.rejected).toBe(true);
+ expect(spec.results().getItems().length).toEqual(1);
+ expect(spec.results().getItems()[0].passed()).toEqual(false);
+ expect(spec.results().getItems()[0].message).toEqual('Error: I should not be.');
+ });
+ });
+
+ it("should run asynchronus tests that return fulfilling promises with unexpected values", function () {
+ var done, spec;
+ runs(function () {
+ env.describe('promise test', function () {
+ spec = env.it('should resolve', function () {
+ return {
+ then: function (resolve, reject) {
+ setTimeout(function() {
+ resolve('I should not be.');
+ done = true;
+ }, 0);
+ }
+ }
+ });
+ });
+ spec.execute();
+ });
+ waitsFor(function () {
+ return done;
+ });
+ runs(function () {
+ expect(spec.results().getItems().length).toEqual(1);
+ expect(spec.results().getItems()[0].passed()).toEqual(false);
+ expect(spec.results().getItems()[0].message).toEqual('Error: Promise fulfilled with unexpected value: I should not be.');
+ });
+ });
+
+ it("should run asynchronus tests that return promises that throw errors", function () {
+ var done, spec;
+ runs(function () {
+ env.describe('promise test', function () {
+ spec = env.it('should resolve', function () {
+ return {
+ then: function (resolve, reject) {
+ throw new Error("I should not be.");
+ }
+ }
+ });
+ });
+ spec.execute();
+ expect(spec.results().getItems().length).toEqual(1);
+ expect(spec.results().getItems()[0].passed()).toEqual(false);
+ expect(spec.results().getItems()[0].message).toEqual("Error: I should not be.");
+ });
+ });
+
it("testAsyncSpecsWithMockSuite", function () {
var bar = 0;
var another_spec;
52 src/core/Block.js
View
@@ -1,6 +1,10 @@
/**
* Blocks are functions with executable code that make up a spec.
*
+ * A block function may return a "thenable" promise, in which case the
+ * test completes when the promise is resolved and fails if the promise
+ * fails or if it succeeds with an unexpected value.
+ *
* @constructor
* @param {jasmine.Env} env
* @param {Function} func
@@ -12,11 +16,49 @@ jasmine.Block = function(env, func, spec) {
this.spec = spec;
};
-jasmine.Block.prototype.execute = function(onComplete) {
+jasmine.Block.prototype.execute = function(onComplete) {
+ var spec = this.spec;
+ var result;
try {
- this.func.apply(this.spec);
- } catch (e) {
- this.spec.fail(e);
+ result = this.func.apply(spec);
+ } catch (error) {
+ spec.fail(error);
+ }
+ if (typeof result === 'undefined') {
+ // blocks that do not return promises complete immediately
+ onComplete();
+ } else if (typeof result !== 'object' || typeof result.then !== 'function') {
+ // if a block returns anything, it must return a promise as defined by
+ // CommonJS/A
+ spec.fail(new Error('`it` block returns non-promise: ' + result));
+ onComplete();
+ } else {
+ // Throwing an error from an attempt to use a returned promise fails
+ // the block
+ try {
+ result.then(function (value) {
+ // fulfillment
+ spec.resolved = true; // for verification;
+ // test block promises must fulfill to undefined. it is typical
+ // to pipe the final promise of a test to make note of an expected
+ // value and return no value
+ if (value !== undefined) {
+ spec.fail(new Error('Promise fulfilled with unexpected value: ' + value));
+ }
+ onComplete();
+ }, function (error) {
+ // rejection
+ spec.rejected = true; // for verification
+ if (!error || !('stack' in error)) {
+ spec.fail(new Error(error));
+ }
+ spec.fail(error);
+ onComplete();
+ });
+ } catch (error) {
+ spec.fail(error);
+ onComplete();
+ }
}
- onComplete();
};
+
Something went wrong with that request. Please try again.