testdouble
Advanced tools
Comparing version 1.1.3 to 1.2.0
@@ -25,3 +25,3 @@ # Stubbing behavior | ||
The `when()` function returns an object containing the function `thenReturn`, which is used to specify the value the test double should return if it's invoked as specified in the `when()` call. For now, only `thenReturn` is supported as a response type. For whatever reason, we haven't found an urgent need to implement two other typical responses, `thenDo` (to invoke a function that has some side effect, [#8](https://github.com/testdouble/testdouble.js/issues/8)) and `thenThrow` (to throw some error, [#7](https://github.com/testdouble/testdouble.js/issues/7)), but we'd gladly accept pull requests for either. | ||
The `when()` function returns an object containing the function `thenReturn`, which is used to specify the value the test double should return if it's invoked as specified in the `when()` call. There are also a `thenDo` and `thenThrow` response type which are described below. | ||
@@ -247,2 +247,15 @@ ## Simple, precise argument stubbing | ||
#### td.matchers.not() | ||
When you only care that a test double function _isn't_ passed a certain value, | ||
you can use the `not` matcher: | ||
``` js | ||
var didSucceed = td.function() | ||
didSucceed(true) | ||
td.verify(didSucceed(td.matchers.not(false))) | ||
``` | ||
### Stubbing callback APIs | ||
@@ -249,0 +262,0 @@ |
@@ -10,5 +10,9 @@ ### Custom argument matchers | ||
when it matches and falsey when it doesn't is considered a matcher by | ||
testdouble.js. Anything without a `__matches` function will only match an | ||
testdouble.js. That said, we provide a `td.matchers.create()` helper for creating | ||
your own custom matchers in a way that'll help ensure your users will get better | ||
messages from `td.explain` calls and `td.verify` failures. | ||
(For the record, arguments without a `__matches` property will only match an | ||
actual invocation if it passes lodash's deep | ||
[_.isEqual](https://lodash.com/docs#isEqual) test. | ||
[_.isEqual](https://lodash.com/docs#isEqual) test.) | ||
@@ -24,9 +28,9 @@ The examples in this document assume you've aliased `testdouble` to `td`. | ||
``` javascript | ||
isA = function(expected) { | ||
return { | ||
__matches: function(actual) { | ||
return actual instanceof expected; | ||
} | ||
isA = td.matchers.create({ | ||
name: 'isA', | ||
matches: function(matcherArgs, actual) { | ||
var expected = matcherArgs[0] | ||
return actual instanceof expected | ||
} | ||
} | ||
}) | ||
``` | ||
@@ -44,1 +48,22 @@ | ||
``` | ||
#### td.matchers.create API | ||
The `create` function takes a configuration object with the following properties | ||
* **matches(matcherArgs, actual)** - _required_ - a function that returns truthy | ||
when an `actual` argument satisfies the matcher's rules, given what the user | ||
passed to the matcher as `matcherArgs` when setting it up. For instance, if | ||
`td.when(func(isFooBar('foo','bar')).thenReturn('baz')` is configured, then | ||
`func('biz')` is invoked, then `isFooBar`'s `matches` function will be invoked | ||
with `matcherArgs` of `['foo','bar']` and `actual` of `'biz'` | ||
* **name** - _optional_ - a string name for better messages. A function can | ||
also be provided, which will be passed the user's `matcherArgs` and should return | ||
a string name | ||
* **onCreate(matcherInstance, matcherArgs)** - _optional_ - a function invoked | ||
whenever an instance of a matcher is created to give the matcher author an | ||
opportunity to mutate the matcher instance or have some other side effect. The | ||
`td.callback` functionality of the library depends on this option | ||
For some examples of `td.matchers.create()` in action, check out the | ||
[built-in matchers](src/matchers/index.coffee) provided by testdouble.js. |
// Generated by CoffeeScript 1.10.0 | ||
(function() { | ||
var _, argumentMatchesExpectation, matcherHidingInExpectedArgument, satisfiesEqualityPlusAnyArgumentMatchers; | ||
var _, argumentMatchesExpectation, arityMismatch, equalsWithMatchers, matcherFor; | ||
@@ -8,16 +8,15 @@ _ = require('lodash'); | ||
module.exports = function(expectedArgs, actualArgs, config) { | ||
if (_.eq(expectedArgs, actualArgs)) { | ||
return true; | ||
} | ||
if (expectedArgs.length !== actualArgs.length && !config.ignoreExtraArgs) { | ||
if (arityMismatch(expectedArgs, actualArgs, config)) { | ||
return false; | ||
} | ||
return satisfiesEqualityPlusAnyArgumentMatchers(expectedArgs, actualArgs); | ||
return equalsWithMatchers(expectedArgs, actualArgs); | ||
}; | ||
satisfiesEqualityPlusAnyArgumentMatchers = function(expectedArgs, actualArgs) { | ||
return _.eq(expectedArgs, actualArgs, function(expected, actual) { | ||
return _.all(expectedArgs, function(expectedArg, i) { | ||
return argumentMatchesExpectation(expectedArg, actualArgs[i]); | ||
}); | ||
arityMismatch = function(expectedArgs, actualArgs, config) { | ||
return expectedArgs.length !== actualArgs.length && !config.ignoreExtraArgs; | ||
}; | ||
equalsWithMatchers = function(expectedArgs, actualArgs) { | ||
return _.all(expectedArgs, function(expectedArg, i) { | ||
return argumentMatchesExpectation(expectedArg, actualArgs[i]); | ||
}); | ||
@@ -28,12 +27,10 @@ }; | ||
var matcher; | ||
if (_.eq(expectedArg, actualArg)) { | ||
return true; | ||
} else if (matcher = matcherHidingInExpectedArgument(expectedArg)) { | ||
if (matcher = matcherFor(expectedArg)) { | ||
return matcher(actualArg); | ||
} else { | ||
return false; | ||
return _.eq(expectedArg, actualArg); | ||
} | ||
}; | ||
matcherHidingInExpectedArgument = function(expectedArg) { | ||
matcherFor = function(expectedArg) { | ||
if (_.isFunction(expectedArg != null ? expectedArg.__matches : void 0)) { | ||
@@ -40,0 +37,0 @@ return expectedArg.__matches; |
// Generated by CoffeeScript 1.10.0 | ||
(function() { | ||
var _, callDescription, callsStore, nullDescription, store, stringifyArgs, stringifyName, stubbingDescription, stubbingsStore; | ||
var _, callDescription, callsStore, nullDescription, store, stringifyArgs, stringifyName, stubbingDescription, stubbingsStore, testdoubleDescription; | ||
@@ -13,3 +13,3 @@ _ = require('lodash'); | ||
stringifyArgs = require('./stringify-args'); | ||
stringifyArgs = require('./stringify/arguments'); | ||
@@ -26,3 +26,3 @@ module.exports = function(testDouble) { | ||
calls: calls, | ||
description: ("This test double " + (stringifyName(testDouble)) + "has " + stubs.length + " stubbings and " + calls.length + " invocations.") + stubbingDescription(stubs) + callDescription(calls), | ||
description: testdoubleDescription(testDouble, stubs, calls) + stubbingDescription(stubs) + callDescription(calls), | ||
isTestDouble: true | ||
@@ -41,2 +41,6 @@ }; | ||
testdoubleDescription = function(testDouble, stubs, calls) { | ||
return "This test double " + (stringifyName(testDouble)) + "has " + stubs.length + " stubbings and " + calls.length + " invocations."; | ||
}; | ||
stubbingDescription = function(stubs) { | ||
@@ -43,0 +47,0 @@ if (stubs.length === 0) { |
// Generated by CoffeeScript 1.10.0 | ||
(function() { | ||
var _, callback, | ||
slice = [].slice; | ||
var _, callback, create; | ||
_ = require('lodash'); | ||
module.exports = callback = function() { | ||
var args; | ||
args = 1 <= arguments.length ? slice.call(arguments, 0) : []; | ||
return { | ||
args: args, | ||
__testdouble_callback: true, | ||
__matches: _.isFunction | ||
}; | ||
}; | ||
create = require('./create'); | ||
module.exports = callback = create({ | ||
name: 'callback', | ||
matches: function(matcherArgs, actual) { | ||
return _.isFunction(actual); | ||
}, | ||
onCreate: function(matcherInstance, matcherArgs) { | ||
matcherInstance.args = matcherArgs; | ||
return matcherInstance.__testdouble_callback = true; | ||
} | ||
}); | ||
callback.__name = 'callback'; | ||
callback.__matches = _.isFunction; | ||
@@ -19,0 +23,0 @@ |
// Generated by CoffeeScript 1.10.0 | ||
(function() { | ||
var create; | ||
create = require('./create'); | ||
module.exports = function() { | ||
var captor; | ||
return captor = { | ||
capture: function() { | ||
return { | ||
__matches: function(actual) { | ||
captor.value = actual; | ||
return true; | ||
} | ||
}; | ||
} | ||
capture: create({ | ||
name: 'captor.capture', | ||
matches: function(matcherArgs, actual) { | ||
captor.value = actual; | ||
return true; | ||
} | ||
}) | ||
}; | ||
@@ -15,0 +18,0 @@ }; |
// Generated by CoffeeScript 1.10.0 | ||
(function() { | ||
var _, captor, | ||
slice = [].slice; | ||
var _, create, stringifyArguments; | ||
_ = require('lodash'); | ||
captor = require('./captor'); | ||
create = require('./create'); | ||
stringifyArguments = require('../stringify/arguments'); | ||
module.exports = { | ||
captor: captor, | ||
isA: function(type) { | ||
return { | ||
__matches: function(actual) { | ||
if (type === Number) { | ||
return _.isNumber(actual); | ||
} else if (type === String) { | ||
return _.isString(actual); | ||
} else if (type === Boolean) { | ||
return _.isBoolean(actual); | ||
} else { | ||
return actual instanceof type; | ||
} | ||
create: create, | ||
captor: require('./captor'), | ||
isA: create({ | ||
name: function(matcherArgs) { | ||
var ref, s; | ||
s = ((ref = matcherArgs[0]) != null ? ref.name : void 0) != null ? matcherArgs[0].name : stringifyArguments(matcherArgs); | ||
return "isA(" + s + ")"; | ||
}, | ||
matches: function(matcherArgs, actual) { | ||
var type; | ||
type = matcherArgs[0]; | ||
if (type === Number) { | ||
return _.isNumber(actual); | ||
} else if (type === String) { | ||
return _.isString(actual); | ||
} else if (type === Boolean) { | ||
return _.isBoolean(actual); | ||
} else { | ||
return actual instanceof type; | ||
} | ||
}; | ||
}, | ||
anything: function() { | ||
return { | ||
__matches: function() { | ||
return true; | ||
} | ||
}; | ||
}, | ||
contains: function() { | ||
var containings, containsAllSpecified; | ||
containings = 1 <= arguments.length ? slice.call(arguments, 0) : []; | ||
containsAllSpecified = function(containing, actual) { | ||
return _.all(containing, function(val, key) { | ||
if (actual == null) { | ||
return false; | ||
} | ||
if (_.isPlainObject(val)) { | ||
return containsAllSpecified(val, actual[key]); | ||
} | ||
}), | ||
anything: create({ | ||
name: 'anything', | ||
matches: function() { | ||
return true; | ||
} | ||
}), | ||
contains: create({ | ||
name: 'contains', | ||
matches: function(containings, actualArg) { | ||
var containsAllSpecified; | ||
containsAllSpecified = function(containing, actual) { | ||
return _.all(containing, function(val, key) { | ||
if (actual == null) { | ||
return false; | ||
} | ||
if (_.isPlainObject(val)) { | ||
return containsAllSpecified(val, actual[key]); | ||
} else { | ||
return _.eq(val, actual[key]); | ||
} | ||
}); | ||
}; | ||
return _.all(containings, function(containing) { | ||
if (_.isString(containing)) { | ||
return _.include(actualArg, containing); | ||
} else if (_.isArray(containing)) { | ||
return _.any(actualArg, function(actualElement) { | ||
return _.eq(actualElement, containing); | ||
}); | ||
} else if (_.isPlainObject(containing)) { | ||
return containsAllSpecified(containing, actualArg); | ||
} else { | ||
return _.eq(val, actual[key]); | ||
throw new Error("the contains() matcher only supports strings, arrays, and plain objects"); | ||
} | ||
}); | ||
}; | ||
return { | ||
__matches: function(actual) { | ||
return _.all(containings, function(containing) { | ||
if (_.isString(containing)) { | ||
return _.include(actual, containing); | ||
} else if (_.isArray(containing)) { | ||
return _.any(actual, function(actualElement) { | ||
return _.eq(actualElement, containing); | ||
}); | ||
} else if (_.isPlainObject(containing)) { | ||
return containsAllSpecified(containing, actual); | ||
} else { | ||
throw new Error("the contains() matcher only supports strings, arrays, and plain objects"); | ||
} | ||
}); | ||
} | ||
}; | ||
}, | ||
argThat: function(predicate) { | ||
return { | ||
__matches: function(actual) { | ||
return predicate(actual); | ||
} | ||
}; | ||
} | ||
} | ||
}), | ||
argThat: create({ | ||
name: 'argThat', | ||
matches: function(matcherArgs, actual) { | ||
var predicate; | ||
predicate = matcherArgs[0]; | ||
return predicate(actual); | ||
} | ||
}), | ||
not: create({ | ||
name: 'not', | ||
matches: function(matcherArgs, actual) { | ||
var expected; | ||
expected = matcherArgs[0]; | ||
return !_.eq(expected, actual); | ||
} | ||
}) | ||
}; | ||
}).call(this); |
@@ -11,3 +11,3 @@ // Generated by CoffeeScript 1.10.0 | ||
stringifyArgs = require('./stringify-args'); | ||
stringifyArgs = require('./stringify/arguments'); | ||
@@ -14,0 +14,0 @@ module.exports = function(__userDoesPretendInvocationHere__, config) { |
// Generated by CoffeeScript 1.10.0 | ||
(function() { | ||
module.exports = '1.1.3'; | ||
module.exports = '1.2.0'; | ||
}).call(this); |
{ | ||
"name": "testdouble", | ||
"version": "1.1.3", | ||
"version": "1.2.0", | ||
"description": "A minimal test double library for TDD with JavaScript", | ||
@@ -22,3 +22,3 @@ "homepage": "https://github.com/testdouble/testdouble.js", | ||
"compile": "npm run compile:node && npm run compile:test && npm run compile:browser", | ||
"test": "mocha --ui mocha-given --reporter $npm_package_config_mocha_reporter --compilers coffee:coffee-script --recursive test/node-helper.coffee test/lib", | ||
"test": "mocha --ui mocha-given --reporter $npm_package_config_mocha_reporter --compilers coffee:coffee-script --recursive test/node-helper.coffee test/src", | ||
"test:browser": "npm run compile && testem ci", | ||
@@ -44,3 +44,4 @@ "test:example:webpack": "cd examples/webpack && npm i && npm test & cd ../..", | ||
"lodash": "^3.10.1", | ||
"quibble": "^0.3.0" | ||
"quibble": "^0.3.0", | ||
"stringify-object-with-one-liners": "^1.0.0" | ||
}, | ||
@@ -47,0 +48,0 @@ "devDependencies": { |
@@ -15,2 +15,23 @@ # testdouble.js | ||
## Coming from Sinon.js? | ||
Right now, Sinon.js is the test double incumbent in JavaScript, with over 1.7 | ||
million downloads in the last month. If you've got experience with Sinon, [check | ||
out our side-by-side | ||
comparison](http://blog.testdouble.com/posts/2016-03-13-testdouble-vs-sinon.html) | ||
to see why we wrote testdouble.js and how some of the API translates. | ||
## The Very Basics | ||
Before diving into our in-depth docs, here are a couple demo GIFs of the basic | ||
uses: | ||
### Stubbing return values for functions | ||
![simple stubbing example animation](docs/img/stub.gif) | ||
### Verifying a function was invoked | ||
![simple verification of a function invocation](docs/img/verify.gif) | ||
## Docs | ||
@@ -50,2 +71,3 @@ | ||
4. [td.matchers.argThat()](docs/5-stubbing-results.md#tdmatchersargthat) | ||
5. [td.matchers.not()](docs/5-stubbing-results.md#tdmatchersnot) | ||
6. [Stubbing callback APIs](docs/5-stubbing-results.md#stubbing-callback-apis) | ||
@@ -52,0 +74,0 @@ 7. [Stub exceptions with thenThrow](docs/5-stubbing-results.md#stub-exceptions-with-thenthrow) |
@@ -22,4 +22,4 @@ var pkg = require('./package.json'); | ||
// tests | ||
"generated/test/lib/**/*.js" | ||
"generated/test/src/**/*.js" | ||
] | ||
}; | ||
} |
Sorry, the diff of this file is too big to display
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
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
1234511
145
16453
105
3
+ Addedis-plain-obj@1.1.0(transitive)
+ Addedis-regexp@1.0.0(transitive)
+ Addedstringify-object-with-one-liners@1.0.0(transitive)