Skip to content

Instantly share code, notes, and snippets.

@jonnyreeves
Created June 2, 2012 13:32
Show Gist options
  • Star 37 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save jonnyreeves/2858452 to your computer and use it in GitHub Desktop.
Save jonnyreeves/2858452 to your computer and use it in GitHub Desktop.
Unit Testing Promises with Sinon.js
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<link rel="stylesheet" href="http://code.jquery.com/qunit/git/qunit.css" type="text/css" media="screen" />
<!-- when.js Promises implementation -->
<script src="https://raw.github.com/cujojs/when/master/when.js"></script>
<!-- Unit testing and mocking framework -->
<script type="text/javascript" src="http://code.jquery.com/qunit/git/qunit.js"></script>
<script type="text/javascript" src="http://sinonjs.org/releases/sinon-1.3.4.js"></script>
<script type="text/javascript" src="http://sinonjs.org/releases/sinon-qunit-1.0.0.js"></script>
<!-- test suite -->
<script src="whenjs-examples.js"></script>
</head>
<body>
<h1 id="qunit-header">Unit Testing when.js Promises with QUnit and SinonJS</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture"></div>
</body>
</html>
function createClient() {
return {
// Classic async method which returns an un-resolved promise.
fetchAsync: function () {
var dfd = when.defer();
// This contrived example isn't the important bit, replace this
// with a jQuery.get call, or any other async action which yeilds
// a promise to the caller.
setTimeout(function () {
dfd.resolve("Response");
}, 1500);
return dfd.promise;
},
// Slightly different take, this time a Promise resolver is supplied
// to the method.
asyncInit: function (completionPromise) {
// Again, the implementation isn't important - but after an async
// delay this method will either resolve, or reject the supplied
// connection promise.
setTimeout(function () {
completionPromise.resolve("I'm done!");
}, 1500);
// Note the lack of return statement.
}
}
};
// QUnit's way of defining a TestSuite. The 'setup' method below will be
// invoked before each 'test' case.
module("when.js examples", {
// Setup is invoked before each test run.
setup: function () {
// Create a fresh test client instance to work with.
this.client = createClient();
}
});
test("Stub a promise to always return a resolved Promise", function () {
// Stub the promise so it returns a fully resolved promise.
var expectedResponse = "what we expect to get back... eventaully";
sinon.stub(this.client, 'fetchAsync').returns(when(expectedResponse));
// Call the method and run the assertion
this.client.fetchAsync().then(function (actualResponse) {
strictEqual(actualResponse, expectedResponse, "fetchAsync() returned a"
+ " resolved Promise with the expected response");
});
});
test("Stub a promise to always return a rejected Promise", function () {
// This time we make 'fetchAsync' return a rejected promise
var expectedError = new Error("KaBOOM!");
sinon.stub(this.client, 'fetchAsync').returns(when.reject(expectedError));
// Call the method and expect it to fail.
this.client.fetchAsync().otherwise(function (actualError) {
strictEqual(actualError, expectedError, "fetchAsync() returned a"
+ " rejected Promise with the expected error");
});
});
test("Stub a promise so the testcase has control over it", function () {
// Sometimes you want the testcase to hold onto the Promise so you can
// assert the state of other parts of the system while the Promise is
// still un-resolved. Start by creating a new Promise.
var fetchPromise = when.defer();
// Now stub fetchAsync so it returns this Promise.
sinon.stub(this.client, 'fetchAsync').returns(fetchPromise);
// Some example state, just to show we have control over the Promise.
var didWeResolveThePromiseYet = false;
// Call the method, but it won't have resolved yet...
this.client.fetchAsync().then(function () {
equal(didWeResolveThePromiseYet, true, "Test case dictated when the"
+ " Promise resolved");
});
// Ok, now let's flip that flag.
didWeResolveThePromiseYet = true;
// ... and resolve the promise.
fetchPromise.resolve("Go for it!");
});
test("Stub a method so it resolves the supplied promise", function () {
// Stub the asyncInit method so it calls the 'resolve' method of the
// supplied promise.
var expectedResult = "we get this back once asyncInit's done it's thing";
sinon.stub(this.client, 'asyncInit').yieldsTo("resolve", expectedResult);
// This is the promise we supply to asyncInit().
var initDeffered = when.defer();
// Attach our assertion to the completion handler.
initDeffered.promise.then(function (actualResult) {
strictEqual(actualResult, expectedResult, "asyncInit() resolves the"
+ " supplied Promise with the expected result");
});
// Invoke the client supplying the resolver
this.client.asyncInit(initDeffered.resolver);
});
test("Stub a method is it rejects the supplied promise", function () {
// This time asyncInit will always reject the supplied Promise.
var expectedError = new Error("Ain't gonna happen");
sinon.stub(this.client, 'asyncInit').yieldsTo("reject", expectedError);
// This is the deffered we supply to asyncInit().
var initDeffered = when.defer();
// Attach our assertion to the error handler.
initDeffered.promise.otherwise(function (actualError) {
strictEqual(actualError, expectedError, "asyncInit() rejects the"
+ " supplied Promise with the expected error");
});
// Invoke the client supplying the resolver.
this.client.asyncInit(initDeffered.resolver);
});
@briancavalier
Copy link

This is good stuff! Unit testing with promises is def territory that needs more work and examples like this. Using sinon's yieldTo to trigger resolution/rejection is especially clever.

Another approach that I've seen used is to actually mock or fake the promises themselves. For example, in some cases, it may make sense to stub a function and have it do something like this:

function fakeResolved(value) {
    return {
        then: function(callback) {
            callback(expectedResponse);
        }
    }
}

sinon.stub(this.client, 'fetchAsync').returns(fakeResolved(expectedResult));

In some cases, that may be enough. If the code consuming the promise uses when(), that'll be even safer, since when() will defend it against any weird side effects of using a fake promise.

I'd be interested to hear what you think about using fake promises like that.

One potential gotcha for folks to remember about using real promises is that some promise implementations, such as Q, force callbacks to be invoked in a future turn (i.e. asynchronously). There are valid reasons for doing that, but most of the popular implementations, like when.js, dojo Deferred, and jQuery Deferred, don't do that. Anyway, tests that might be expecting synchronous promise behavior might break if an async-forcing promise somehow made its way into the testing promise chain.

Anyway, great work, dude.

P.S. you can use when/delay to implement fetchAsync and asyncInit:

return {
    fetchAsync: function() {
        // Returns a promise that resolves with the value "Response"
        // in 1500 millis.
        return delay("Response", 1500);
    },

    asyncInit: function(completionPromise) {
        // After 1500 millis, resolve completionPromise with
        // the value "I'm done"
        when.chain(delay("I'm done", 1500), completionPromise); 
    }   
};

@muhammadghazali
Copy link

@jonnyreeves This gist really is helpful for me. I was struggled to unit testing the promises method until I found this gist. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment