sinon-test
Advanced tools
Comparing version 0.0.1 to 1.0.0
135
lib/test.js
@@ -13,3 +13,60 @@ /** | ||
var slice = Array.prototype.slice; | ||
var DEFAULT_CONFIG = { | ||
injectIntoThis: true, | ||
injectInto: null, | ||
properties: ["spy", "stub", "mock", "clock", "server", "requests"], | ||
useFakeTimers: true, | ||
useFakeServer: true | ||
}; | ||
function finish(sandbox, error, dontThrow) { | ||
if (error) { | ||
sandbox.restore(); | ||
if (dontThrow) { | ||
return; | ||
} | ||
throw error; | ||
} | ||
sandbox.verifyAndRestore(); | ||
} | ||
function handleFn(sandbox, result) { | ||
if (result && utils.isPromise(result)) { | ||
return result.then( | ||
function sinonHandlePromiseResolve(value) { | ||
finish(sandbox); | ||
return value; | ||
}, | ||
function sinonHandlePromiseReject(error) { | ||
finish( | ||
sandbox, | ||
error || new Error("Promise rejected with no/falsy error") | ||
); | ||
} | ||
); | ||
} | ||
finish(sandbox); | ||
return result; | ||
} | ||
function handleAsyncFn(sandbox, result, isAsync) { | ||
if (result && utils.isPromise(result)) { | ||
finish(sandbox, new Error( | ||
"Your test should take a callback *or* return a promise. " | ||
+ "It should not do both." | ||
)); | ||
} | ||
// the function had an arity of 1 or more, but it was not passed a callback | ||
if (!isAsync) { | ||
finish(sandbox); | ||
} | ||
} | ||
function configure(sinon, config) { | ||
@@ -20,10 +77,23 @@ if (!utils.isSinon(sinon)) { | ||
if (!config) { | ||
config = { | ||
injectIntoThis: true, | ||
injectInto: null, | ||
properties: ["spy", "stub", "mock", "clock", "server", "requests"], | ||
useFakeTimers: true, | ||
useFakeServer: true | ||
}; | ||
function callSandboxedFn(context, args, fn, handler) { | ||
config = sinon.getConfig(config || DEFAULT_CONFIG); | ||
config.injectInto = config.injectIntoThis && context || config.injectInto; | ||
var sandbox = sinon.sandbox.create(config); | ||
var done = args.length && args[args.length - 1]; | ||
var result; | ||
if (typeof done === "function") { | ||
args[args.length - 1] = function sinonDone(error) { | ||
finish(sandbox, error, true); | ||
done(error); | ||
}; | ||
} | ||
try { | ||
result = fn.apply(context, args.concat(sandbox.args)); | ||
} catch (e) { | ||
finish(sandbox, e); | ||
} | ||
return handler(sandbox, result, typeof done === "function"); | ||
} | ||
@@ -38,47 +108,10 @@ | ||
function sinonSandboxedTest() { | ||
config = sinon.getConfig(config); | ||
config.injectInto = config.injectIntoThis && this || config.injectInto; | ||
var sandbox = sinon.sandbox.create(config); | ||
var args = slice.call(arguments); | ||
var oldDone = args.length && args[args.length - 1]; | ||
var exception, result; | ||
if (typeof oldDone === "function") { | ||
args[args.length - 1] = function sinonDone(res) { | ||
if (res) { | ||
sandbox.restore(); | ||
} else { | ||
sandbox.verifyAndRestore(); | ||
} | ||
oldDone(res); | ||
}; | ||
return callback.length | ||
? function sinonAsyncSandboxedTest(_) { // eslint-disable-line no-unused-vars | ||
return callSandboxedFn(this, slice.call(arguments), callback, handleAsyncFn); | ||
} | ||
try { | ||
result = callback.apply(this, args.concat(sandbox.args)); | ||
} catch (e) { | ||
exception = e; | ||
: function sinonSandboxedTest() { | ||
return callSandboxedFn(this, slice.call(arguments), callback, handleFn); | ||
} | ||
if (typeof oldDone !== "function") { | ||
if (typeof exception !== "undefined") { | ||
sandbox.restore(); | ||
throw exception; | ||
} else { | ||
sandbox.verifyAndRestore(); | ||
} | ||
} | ||
return result; | ||
} | ||
if (callback.length) { | ||
return function sinonAsyncSandboxedTest(done) { // eslint-disable-line no-unused-vars | ||
return sinonSandboxedTest.apply(this, arguments); | ||
}; | ||
} | ||
return sinonSandboxedTest; | ||
; | ||
}; | ||
@@ -85,0 +118,0 @@ } |
@@ -11,2 +11,6 @@ /** | ||
exports.isPromise = function (object) { | ||
return typeof object === "object" && typeof object.then === "function"; | ||
}; | ||
exports.isSinon = function (obj) { | ||
@@ -13,0 +17,0 @@ return !!obj && typeof obj === "object" && |
{ | ||
"name": "sinon-test", | ||
"version": "0.0.1", | ||
"version": "1.0.0", | ||
"description": "", | ||
"main": "lib/index.js", | ||
"scripts": { | ||
"test": "./scripts/ci-test.sh", | ||
"build": "rollup -c | uglifyjs --mangle --compress if_return,dead_code,unsafe,unsafe_comps,join_vars,comparisons,loops,collapse_vars,cascade,pure_getters > dist/sinon-test.js", | ||
"coverage": "nyc report --reporter text-lcov | coveralls", | ||
"test": "nyc mocha", | ||
"lint": "eslint .", | ||
@@ -21,12 +23,18 @@ "eslint-pre-commit": "./scripts/eslint-pre-commit" | ||
"devDependencies": { | ||
"browserify": "^13.0.0", | ||
"browserify-shim": "^3.8.12", | ||
"buster": "0.7.18", | ||
"buster-core": "^0.6.4", | ||
"buster-istanbul": "^0.1.15", | ||
"eslint": "^1.7.1", | ||
"eslint-config-defaults": "^2.1.0", | ||
"es6-promise": "^4.0.5", | ||
"eslint": "^3.7.1", | ||
"eslint-config-defaults": "^9.0.0", | ||
"mocha": "^3.0.2", | ||
"nyc": "^8.3.0", | ||
"phantomjs-prebuilt": "^2.1.7", | ||
"pre-commit": "^1.1.2", | ||
"sinon": "git+https://github.com/sinonjs/sinon.git" | ||
"rollup": "^0.36.1", | ||
"rollup-plugin-commonjs": "^5.0.4", | ||
"sinon": "^2.0.0-pre", | ||
"uglify-js": "^2.7.3" | ||
}, | ||
"peerDependencies": { | ||
"sinon": "^2.0.0-pre" | ||
}, | ||
"browserify-shim": { | ||
@@ -33,0 +41,0 @@ "buster": "global:buster" |
# Sinon Test | ||
> Automatic sandbox setup and teardown for SinonJS | ||
Test Framework helpers for SinonJS | ||
## Why? | ||
Instead of writing tedious setup and teardown code for each | ||
individual test case you can let Sinon do all the cleanup for you. | ||
So instead of doing this (using [Mocha](https://mochajs.org/) syntax): | ||
```javascript | ||
var spy1; | ||
var spy2; | ||
afterEach(()=>{ | ||
spy1.restore(); | ||
spy2.restore(); | ||
}); | ||
it('should do something', ()=>{ | ||
spy1 = sinon.spy(myFunc); | ||
spy2 = sinon.spy(myOtherFunc); | ||
myFunc(1); | ||
myFunc(2); | ||
assert(spy1.calledWith(1)); | ||
assert(spy1.calledWith(2)); | ||
}); | ||
``` | ||
You could write just this | ||
```javascript | ||
it('should do something', sinon.test(function(){ | ||
var spy1 = this.spy(myFunc); | ||
var spy2 = this.spy(myOtherFunc); | ||
myFunc(1); | ||
myFunc(2); | ||
assert(spy1.calledWith(1)); | ||
assert(spy1.calledWith(2)); | ||
})); //auto-cleanup | ||
``` | ||
Sinon will take care of removing all the spies and stubs | ||
from the wrapped functions for you. It does this by using | ||
`sinon.sandbox` internally. | ||
Do notice that | ||
we use a `function` and not a arrow function (ES2015) | ||
when wrapping the test with `sinon.test` as it needs | ||
to be able to access the `this` pointer used inside | ||
of the function. | ||
See the [Usage](#usage) section for more details. | ||
## Installation | ||
@@ -11,7 +60,11 @@ | ||
or just add it as a `<script src="dist/sinon-test.js"></script>` | ||
tag to the html where you write your tests. A pre-built browser | ||
version is found in [dist/sinon-test.js](./dist/sinon-test.js). | ||
## Usage | ||
See the [sinon project homepage](http://sinonjs.org/) for documentation on usage. | ||
See the [sinon documentation](http://sinonjs.org/docs/#sinon-test) for documentation on usage. | ||
`sinon-test` instances need to be configured with a `sinon` instance before they can be used, you can emulate the sinon 1.x methods with the following: | ||
`sinon-test` instances need to be configured with a `sinon` instance (version 2+) before they can be used; you can emulate the sinon 1.x methods with the following: | ||
@@ -21,5 +74,24 @@ ```js | ||
var sinonTest = require('sinon-test'); | ||
var assert = require('assert'); | ||
sinon.test = sinonTest.configureTest(sinon); | ||
sinon.testCase = sinonTest.configureTestCase(sinon); | ||
describe('my function', function() { | ||
var myFunc = require('./my-func'); | ||
it('should do something', sinon.test(function(){ | ||
var spy = this.spy(myFunc); | ||
myFunc(1); | ||
assert(spy.calledWith(1)); | ||
})); //auto-cleanup | ||
}); | ||
``` | ||
In order to configure with options, a configuration hash can be passed as a 2nd argument to `sinonTest.configureTest`: | ||
```js | ||
sinon.test = sinonTest.configureTest(sinon, {useFakeTimers: false}); | ||
``` | ||
@@ -13,4 +13,4 @@ "use strict"; | ||
buster.testCase("sinon-test.testCase", { | ||
setUp: function () { | ||
module.exports = { | ||
beforeEach: function () { | ||
testCaseInstance = sinonTestCase.configure(sinon); | ||
@@ -266,4 +266,2 @@ }, | ||
} | ||
}); | ||
}; |
@@ -6,4 +6,7 @@ "use strict"; | ||
var sinon = require("sinon"); | ||
var Promise = require("es6-promise").Promise; | ||
var nextTick = buster.nextTick; | ||
var nextTick = typeof process !== "undefined" && process.nextTick || function (fn) { | ||
setTimeout(fn, 0); | ||
}; | ||
var assert = buster.assert; | ||
@@ -14,4 +17,33 @@ var refute = buster.refute; | ||
buster.testCase("sinon-test", { | ||
setUp: function () { | ||
// promise-like structure | ||
function stubPromise() { | ||
var thenStub = sinon.stub(); | ||
var promise = { | ||
then: thenStub, | ||
index: 0, | ||
resolve: function (object) { | ||
var callback = thenStub.getCall(this.index++).args[0]; | ||
if (object) { | ||
callback(object); | ||
} else { | ||
callback(); | ||
} | ||
}, | ||
reject: function (error) { | ||
var callback = thenStub.getCall(this.index++).args[1]; | ||
if (error) { | ||
callback(error); | ||
} else { | ||
callback(); | ||
} | ||
} | ||
}; | ||
thenStub.returns(promise); | ||
return promise; | ||
} | ||
module.exports = { | ||
beforeEach: function () { | ||
this.boundTestCase = function () { | ||
@@ -99,2 +131,65 @@ var properties = { | ||
"throws when an async method throws": function () { | ||
var method = function () {}; | ||
var object = { method: method }; | ||
var fakeDone = function () {}; | ||
assert.exception(function () { | ||
instance(function (done) { // eslint-disable-line no-unused-vars | ||
this.stub(object, "method"); | ||
throw new Error(); | ||
}).call({}, fakeDone); | ||
}, "Error"); | ||
}, | ||
"restores stub after promise resolves": function () { | ||
var object = {}; | ||
var promise = instance(function () { | ||
return new Promise(function (resolve) { | ||
nextTick(function () { resolve(object); }); | ||
}); | ||
}).call({}); | ||
return promise.then(function (result) { | ||
assert.same(result, object); | ||
}); | ||
}, | ||
"restores stub after promise is resolved": function () { | ||
var method = function () {}; | ||
var object = { method: method }; | ||
var promise = instance(function () { | ||
this.stub(object, "method"); | ||
return new Promise(function (resolve) { | ||
nextTick(function () { resolve(object); }); | ||
}); | ||
}).call({}); | ||
assert.equals(object.method === method, false); | ||
return promise.then(function () { | ||
assert.same(object.method, method); | ||
}); | ||
}, | ||
"restores stub after promise is rejected": function () { | ||
var method = function () {}; | ||
var object = { method: method }; | ||
var promise = instance(function () { | ||
this.stub(object, "method"); | ||
return new Promise(function (_, reject) { | ||
nextTick(function () { reject(new Error()); }); | ||
}); | ||
}).call({}); | ||
assert.equals(object.method === method, false); | ||
return promise.then(null, function (err) { | ||
assert.equals(err instanceof Error, true); | ||
}); | ||
}, | ||
"restores stub when method throws": function () { | ||
@@ -225,2 +320,191 @@ var method = function () {}; | ||
"async tests should not allow thenables to be returned": function () { | ||
var thenable = { | ||
then: function () { | ||
} | ||
}; | ||
var test = instance(function (_) { // eslint-disable-line no-unused-vars | ||
return thenable; | ||
}); | ||
assert.exception(test, { | ||
message: /callback .* promise.* both/ | ||
}); | ||
}, | ||
"sync tests with thenable return value": { | ||
beforeEach: function () { | ||
var method = this.method = function () {}; | ||
var object = this.object = {method: method}; | ||
var promise = this.promise = stubPromise(); | ||
this.sinonTest = instance(function () { | ||
this.stub(object, "method"); | ||
return promise; | ||
}); | ||
}, | ||
afterEach: function () { | ||
// ensure sandbox is restored | ||
if (this.promise.index < this.promise.then.callCount) { | ||
this.promise.resolve(); | ||
} | ||
}, | ||
"should listen to returned promise": function (done) { | ||
var self = this; | ||
var promise = self.sinonTest.call({}); | ||
assert(promise.then.calledOnce); | ||
assert(promise.then.getCall(0).args.length, 2); | ||
assert.isFunction(promise.then.getCall(0).args[0]); | ||
assert.isFunction(promise.then.getCall(0).args[1]); | ||
// allow any other actions to take place | ||
nextTick(function () { | ||
refute.same(self.object.method, self.method, "should wait to restore stubs"); | ||
done(); | ||
}); | ||
}, | ||
"restores sandbox after promise is fulfilled": function () { | ||
var promise = this.sinonTest.call({}); | ||
promise.resolve(); | ||
assert.same(this.object.method, this.method); | ||
}, | ||
"restores sandbox after promise is rejected": function () { | ||
var promise = this.sinonTest.call({}); | ||
var error = new Error("expected"); | ||
assert.exception( | ||
function () { | ||
promise.reject(error); | ||
}, | ||
{message: "expected"}, | ||
"should re-throw error from rejected promise" | ||
); | ||
assert.same(this.object.method, this.method); | ||
}, | ||
"ensures test rejects with a non-falsy value": function () { | ||
var promise = this.sinonTest.call({}); | ||
assert.exception( | ||
function () { | ||
promise.reject(false); | ||
}, | ||
{message: /rejected.*falsy/} | ||
); | ||
assert.same(this.object.method, this.method); | ||
} | ||
}, | ||
"sync tests with A+ promise returned": { | ||
beforeEach: function () { | ||
if (typeof Promise === "undefined") { | ||
this.skip(); | ||
} | ||
this.method = function () {}; | ||
this.object = {method: this.method}; | ||
}, | ||
"restores the sandbox when the promise is fulfilled": function (done) { | ||
var self = this; | ||
var expected = {}; | ||
var test = instance(function () { | ||
var sandbox = this; | ||
return new Promise(function (resolve) { | ||
sandbox.stub(self.object, "method"); | ||
resolve(expected); | ||
}); | ||
}); | ||
test.call({}).then(function (thing) { | ||
assert.equals(self.object.method, self.method); | ||
assert.same(thing, expected); | ||
done(); | ||
}); | ||
}, | ||
"restores the sandbox when the promise is rejected": function (done) { | ||
var self = this; | ||
var test = instance(function () { | ||
var sandbox = this; | ||
return new Promise(function (_, reject) { | ||
sandbox.stub(self.object, "method"); | ||
reject(); | ||
}); | ||
}); | ||
test.call({}).catch(function () { | ||
assert.equals(self.object.method, self.method); | ||
done(); | ||
}); | ||
}, | ||
"ensures test rejects with a non-falsy value": function (done) { | ||
var self = this; | ||
var test = instance(function () { | ||
return Promise.reject(false); | ||
}); | ||
test.call({}).catch(function (error) { | ||
assert.match(error.message, /rejected.*falsy/); | ||
assert.same(self.object.method, self.method); | ||
done(); | ||
}); | ||
}, | ||
"re-throws the error if the promise is rejected": function (done) { | ||
var expectedError = new Error("expected"); | ||
var test = instance(function () { | ||
return Promise.reject(expectedError); | ||
}); | ||
test.call({}).catch(function (error) { | ||
assert.equals(error, expectedError); | ||
done(); | ||
}); | ||
}, | ||
"verifies mocks when promise is resolved": function (done) { | ||
var self = this; | ||
var test = instance(function () { | ||
var sandbox = this; | ||
return new Promise(function (resolve) { | ||
sandbox.mock(self.object).expects("method").withExactArgs(1).once(); | ||
self.object.method(42); | ||
resolve(); | ||
}); | ||
}); | ||
test.call({}).catch(function (error) { | ||
assert.equals(error.name, "ExpectationError"); | ||
assert.match(error.message, /Expected method\(1\) once/); | ||
done(); | ||
}); | ||
} | ||
}, | ||
"verifies mocks": function () { | ||
@@ -375,6 +659,2 @@ var method = function () {}; | ||
"browser options": { | ||
requiresSupportFor: { | ||
"ajax/browser": typeof XMLHttpRequest !== "undefined" || typeof ActiveXObject !== "undefined" | ||
}, | ||
"yields server when faking xhr": function () { | ||
@@ -614,2 +894,2 @@ var stubbed, mocked, server; | ||
} | ||
}); | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
54466
20
1148
1
96
1
12