diff --git a/docs/release-source/release/sandbox.md b/docs/release-source/release/sandbox.md index ed6824943..4ff9524bf 100644 --- a/docs/release-source/release/sandbox.md +++ b/docs/release-source/release/sandbox.md @@ -62,19 +62,28 @@ sinon.defaultConfig = { } ``` -
-
injectInto
-
The sandbox's methods can be injected into another object for convenience. The injectInto configuration option can name an object to add properties to.
+##### injectInto -
properties
-
What properties to inject. Note that simply naming "server" here is not sufficient to have a server property show up in the target object, you also have to set useFakeServer to true. -
+The sandbox's methods can be injected into another object for convenience. The +`injectInto` configuration option can name an object to add properties to. -
useFakeTimers
-
If true, the sandbox will have a clock property. Can also be an Array of timer properties to fake.
+##### properties -
useFakeServer
-
If true, server and requests properties are added to the sandbox. Can also be an object to use for fake server. The default one is sinon.fakeServer, but if you're using jQuery 1.3.x or some other library that does not set the XHR's onreadystatechange handler, you might want to do: +What properties to inject. Note that simply naming "server" here is not +sufficient to have a `server` property show up in the target object, you also +have to set `useFakeServer` to `true`. + +##### useFakeTimers + +If `true`, the sandbox will have a `clock` property. Can also be an `Array` of +timer properties to fake. + +##### useFakeServer + +If `true`, `server` and `requests` properties are added to the sandbox. Can +also be an object to use for fake server. The default one is `sinon.fakeServer`, +but if you're using jQuery 1.3.x or some other library that does not set the XHR's +`onreadystatechange` handler, you might want to do: ```javascript sinon.config = { @@ -124,6 +133,13 @@ Fakes XHR and binds a server object to the sandbox such that it too is restored Access requests through `sandbox.requests` and server through `sandbox.server` +#### `sandbox.usingPromise(promiseLibrary);` + +Causes all stubs created from the sandbox to return promises using a specific +Promise library instead of the global one when using `stub.rejects` or +`stub.resolves`. Returns the stub to allow chaining. + +*Since `sinon@2.0.0`* #### `sandbox.restore();` diff --git a/docs/release-source/release/stubs.md b/docs/release-source/release/stubs.md index 24360dd52..e3f1ffec6 100644 --- a/docs/release-source/release/stubs.md +++ b/docs/release-source/release/stubs.md @@ -248,10 +248,10 @@ Causes the stub to return a Promise which resolves to the provided value. When constructing the Promise, sinon uses the `Promise.resolve` method. You are responsible for providing a polyfill in environments which do not provide `Promise`. +The Promise library can be overwritten using the `usingPromise` method. *Since `sinon@2.0.0`* - #### `stub.throws();` Causes the stub to throw an exception (`Error`). @@ -273,6 +273,7 @@ Causes the stub to return a Promise which rejects with an exception (`Error`). When constructing the Promise, sinon uses the `Promise.reject` method. You are responsible for providing a polyfill in environments which do not provide `Promise`. +The Promise library can be overwritten using the `usingPromise` method. *Since `sinon@2.0.0`* @@ -333,8 +334,28 @@ Like `callsArg`, but with arguments to pass to the callback. #### `stub.callsArgOnWith(index, context, arg1, arg2, ...);` + Like above but with an additional parameter to pass the `this` context. +#### `stub.usingPromise(promiseLibrary);` + +Causes the stub to return promises using a specific Promise library instead of +the global one when using `stub.rejects` or `stub.resolves`. Returns the stub +to allow chaining. + +```javascript +var myObj = { + saveSomething: sinon.stub().usingPromise(bluebird.Promise).resolves("baz"); +} + +myObj.saveSomething() + .tap(function(actual) { + console.log(actual); // baz + }); +``` + +*Since `sinon@2.0.0`* + #### `stub.yields([arg1, arg2, ...])` Similar to `callsArg`. diff --git a/lib/sinon/behavior.js b/lib/sinon/behavior.js index 8ccd5b4fa..404b31c37 100644 --- a/lib/sinon/behavior.js +++ b/lib/sinon/behavior.js @@ -129,9 +129,9 @@ var proto = { } else if (this.fakeFn) { return this.fakeFn.apply(context, args); } else if (this.resolve) { - return Promise.resolve(this.returnValue); + return (this.promiseLibrary || Promise).resolve(this.returnValue); } else if (this.reject) { - return Promise.reject(this.returnValue); + return (this.promiseLibrary || Promise).reject(this.returnValue); } else if (this.callsThrough) { return this.stub.wrappedMethod.apply(context, args); } diff --git a/lib/sinon/collection.js b/lib/sinon/collection.js index bf0c46de1..a41575a3d 100644 --- a/lib/sinon/collection.js +++ b/lib/sinon/collection.js @@ -71,6 +71,11 @@ var collection = { return fake; }, + addUsingPromise: function (fake) { + fake.usingPromise(this.promiseLibrary); + return fake; + }, + spy: function spy() { return this.add(sinonSpy.apply(sinonSpy, arguments)); }, @@ -85,9 +90,16 @@ var collection = { sinonStub.apply(null, arguments); if (isStubbingEntireObject) { - collectOwnMethods(stubbed).forEach(this.add.bind(this)); + var ownMethods = collectOwnMethods(stubbed); + ownMethods.forEach(this.add.bind(this)); + if (this.promiseLibrary) { + ownMethods.forEach(this.addUsingPromise.bind(this)); + } } else { this.add(stubbed); + if (this.promiseLibrary) { + stubbed.usingPromise(this.promiseLibrary); + } } return stubbed; diff --git a/lib/sinon/default-behaviors.js b/lib/sinon/default-behaviors.js index 469dd75d3..f5bdc46e2 100644 --- a/lib/sinon/default-behaviors.js +++ b/lib/sinon/default-behaviors.js @@ -68,6 +68,10 @@ module.exports = { fake.callbackAsync = false; }, + usingPromise: function usingPromise(fake, promiseLibrary) { + fake.promiseLibrary = promiseLibrary; + }, + yields: function (fake) { fake.callArgAt = useLeftMostCallback; fake.callbackArguments = slice.call(arguments, 1); diff --git a/lib/sinon/sandbox.js b/lib/sinon/sandbox.js index 71cb434cb..4a3d7ce30 100644 --- a/lib/sinon/sandbox.js +++ b/lib/sinon/sandbox.js @@ -90,6 +90,13 @@ extend(sinonSandbox, { return obj; }, + usingPromise: function (promiseLibrary) { + + this.promiseLibrary = promiseLibrary; + + return this; + }, + restore: function () { if (arguments.length) { throw new Error("sandbox.restore() does not take any parameters. Perhaps you meant stub.restore()"); diff --git a/test/sandbox-test.js b/test/sandbox-test.js index 89d0ad1f7..d512bd51d 100644 --- a/test/sandbox-test.js +++ b/test/sandbox-test.js @@ -109,6 +109,71 @@ describe("sinonSandbox", function () { }); }); + describe(".usingPromise", function () { + beforeEach(function () { + this.sandbox = Object.create(sinonSandbox); + }); + + afterEach(function () { + this.sandbox.restore(); + }); + + it("must be a function", function () { + + assert.isFunction(this.sandbox.usingPromise); + }); + + it("must return the sandbox", function () { + var mockPromise = {}; + + var actual = this.sandbox.usingPromise(mockPromise); + + assert.same(actual, this.sandbox); + }); + + it("must set all stubs created from sandbox with mockPromise", function () { + + var resolveValue = {}; + var mockPromise = { + resolve: sinonStub.create().resolves(resolveValue) + }; + + this.sandbox.usingPromise(mockPromise); + var stub = this.sandbox.stub().resolves(); + + return stub() + .then(function (action) { + + assert.same(resolveValue, action); + assert(mockPromise.resolve.calledOnce); + }); + }); + + it("must set all stubs created from sandbox with mockPromise", function () { + + var resolveValue = {}; + var mockPromise = { + resolve: sinonStub.create().resolves(resolveValue) + }; + var stubbedObject = { + stubbedMethod: function () { + return; + } + }; + + this.sandbox.usingPromise(mockPromise); + this.sandbox.stub(stubbedObject); + stubbedObject.stubbedMethod.resolves({}); + + return stubbedObject.stubbedMethod() + .then(function (action) { + + assert.same(resolveValue, action); + assert(mockPromise.resolve.calledOnce); + }); + }); + }); + // These were not run in browsers before, as we were only testing in node if (typeof window !== "undefined") { describe("fake XHR/server", function () { diff --git a/test/stub-test.js b/test/stub-test.js index 0ae518ced..9e4bfc657 100644 --- a/test/stub-test.js +++ b/test/stub-test.js @@ -317,6 +317,59 @@ describe("stub", function () { }); }); + describe(".usingPromise", function () { + it("should exist and be a function", function () { + var stub = createStub.create(); + + assert(stub.usingPromise); + assert.isFunction(stub.usingPromise); + }); + + it("should return the current stub", function () { + var stub = createStub.create(); + + assert.same(stub.usingPromise(Promise), stub); + }); + + it("should set the promise used by resolve", function () { + var stub = createStub.create(); + var promise = { + resolve: createStub.create().callsFake(function (value) { + return Promise.resolve(value); + }) + }; + var object = {}; + + stub.usingPromise(promise).resolves(object); + + return stub().then(function (actual) { + assert.same(actual, object, "Same object resolved"); + assert.isTrue(promise.resolve.calledOnce, "Custom promise resolve called once"); + assert.isTrue(promise.resolve.calledWith(object), "Custom promise resolve called once with expected"); + }); + }); + + it("should set the promise used by reject", function () { + var stub = createStub.create(); + var promise = { + reject: createStub.create().callsFake(function (err) { + return Promise.reject(err); + }) + }; + var reason = new Error(); + + stub.usingPromise(promise).rejects(reason); + + return stub().then(function () { + referee.fail("this should not resolve"); + }).catch(function (actual) { + assert.same(actual, reason, "Same object resolved"); + assert.isTrue(promise.reject.calledOnce, "Custom promise reject called once"); + assert.isTrue(promise.reject.calledWith(reason), "Custom promise reject called once with expected"); + }); + }); + }); + describe(".throws", function () { it("throws specified exception", function () { var stub = createStub.create();