Skip to content

Commit 0c745d5

Browse files
committed
moar
1 parent b49b9b3 commit 0c745d5

File tree

1 file changed

+195
-28
lines changed

1 file changed

+195
-28
lines changed

README.md

Lines changed: 195 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,19 @@ Mocking libraries are more often abused than used effectively, so figuring out
4646
how to document a mocking library in such a way as to only encourage healthy
4747
use has proven to be a real challenge. Here are a few paths to getting started:
4848

49-
* The [API section of this README](#api) to get an at-a-glance view of
50-
the API so you can get started stubbing and verifying right away
49+
* The [API section of this README](#api) to get an at-a-glance view of the API
50+
so you can get started stubbing and verifying right away
5151
* A [20-minute
5252
video](http://blog.testdouble.com/posts/2016-06-05-happier-tdd-with-testdouble-js)
5353
overview of the library, its goals, and the basics of its API
5454
* A [comparison between testdouble.js and
5555
Sinon.js](http://blog.testdouble.com/posts/2016-03-13-testdouble-vs-sinon.html),
5656
in case you've already got experience working with Sinon and you're looking
5757
for a high-level overview of the differences
58-
* The full testdouble.js [documentation](/docs), which is quite lengthy, but
58+
* The full testdouble.js [documentation](/docs), which is lengthier, but
5959
will do a thorough job to explain when to (and when not to) take advantage of
60-
the various faetures of testdouble.js. Its outline is at the [bottom of this
61-
README](#docs)
60+
the various faetures of testdouble.js. Its outline is in
61+
[docs/README.md](/docs#readme)
6262

6363
Of course, if you're unsure of how to approach writing an isolated test with
6464
testdouble.js, we welcome you to [open a issue on GitHub to ask a
@@ -301,37 +301,204 @@ const subject = function (SomeConstructor) {
301301
}
302302
```
303303

304-
---
305-
# old docs:
306-
---
304+
### `td.when()` for stubbing responses
307305

308-
### Stubbing return values for functions
306+
**`td.when(__rehearsal__[, options])`**
307+
308+
Once you have your subject's dependencies replaced with test double functions,
309+
you'll want to be able to to stub return values (and other sorts of responses)
310+
when the subject invokes the test double in the way that the test expects.
311+
312+
To make stubbing configuration easy to read and grep, `td.when()`'s first
313+
argument isn't an argument at all, but rather a placeholder to demonstrate the
314+
way you're expecting the test double to be invoked by the subject, like so:
315+
316+
```js
317+
const increment = td.func()
318+
td.when(increment(5)).thenReturn(6)
319+
```
320+
321+
We would say that `increment(5)` is "rehearsing the invocation". Note that by
322+
default, a stubbing is only satisfied when the subject calls the test double
323+
exactly as it was rehearsed. This can be customized with [argument
324+
matchers](/docs/5-stubbing-results.md#loosening-stubbings-with-argument-matchers),
325+
which allow for rehearsals that do things like
326+
`increment(td.matchers.isA(Number))` or `save(td.matchers.contains({age: 21}))`.
327+
328+
Also note that, `td.when()` takes an [optional configuration
329+
object](/docs/5-stubbing-results.md#configuring-stubbings) as a second
330+
parameter, which enables advanced usage like ignoring extraneous arguments and
331+
limiting the number of times a stubbing can be satisfied.
332+
333+
Calling `td.when()` returns an object of functions that each represent
334+
the type of outcome you want to configure whenever the test double is invoked as
335+
demonstrated by your rehearsal, which we'll describe below, beginning with
336+
`thenReturn`.
337+
338+
#### `td.when().thenReturn()`
339+
340+
**`td.when(__rehearsal__[, options]).thenReturn('some value'[, more, values])`**
341+
342+
The simplest example is when you want to return a specific value in exchange for
343+
a known argument, like so:
309344

310345
```js
311-
var td = require('testdouble');
346+
const loadsPurchases = td.replace('../src/loads-purchases')
347+
td.when(loadsPurchases(2018, 7)).thenReturn(['a purchase', 'another'])
348+
```
312349

313-
var fetch = td.function();
314-
td.when(fetch(42)).thenReturn('Jane User');
350+
Then, in the hands of your subject under test:
315351

316-
fetch(42); // -> 'Jane User'
352+
```js
353+
loadsPurchases(2018, 8) // returns `['a purchase', 'another']`
354+
loadsPurchases(2018, 7) // returns undefined, since no stubbing matched
317355
```
318356

319-
### Verifying a function was invoked
357+
If you're not used to stubbing, it may seem contrived to think a test will know
358+
exactly what argument to pass in and expect back from a dependency, but in an
359+
isolated unit test this is not only feasible but entirely normal and expected!
360+
It can help the test author ensure the test remains minimal and obvious to
361+
future readers.
362+
363+
Note as well that subsequence instances of matching invocations can be stubbed
364+
by passing additional arguments to `thenReturn()`, such that:
320365

321366
```js
322-
var td = require('testdouble');
323-
324-
var save = td.function('.save');
325-
save(41, 'Jane');
326-
327-
td.verify(save(41, 'Jill'));
328-
//
329-
// Error: Unsatisfied verification on test double `.save`.
330-
//
331-
// Wanted:
332-
// - called with `(41, "Jill")`.
333-
//
334-
// But was actually called:
335-
// - called with `(41, "Jane")`.
367+
const hitCounter = td.func()
368+
td.when(hitCounter()).thenReturn(1, 2, 3, 4)
369+
370+
hitCounter() // 1
371+
hitCounter() // 2
372+
hitCounter() // 3
373+
hitCounter() // 4
374+
hitCounter() // 5
336375
```
337376

377+
#### `td.when().thenResolve()` and `td.when().thenReject()`
378+
379+
**`td.when(__rehearsal__[, options]).thenResolve('some value'[, more, values])`**
380+
**`td.when(__rehearsal__[, options]).thenReject('some value'[, more, values])`**
381+
382+
The `thenResolve()` and `thenReject()` stubbings will take whatever value is
383+
passed to them and wrap it in an immediately resolved or rejected promise,
384+
respectively. By default testdouble.js will use whatever `Promise` is globally
385+
defined, but you can specify your own like this:
386+
387+
```js
388+
td.config({promiseConstructor: require('bluebird')})`
389+
```
390+
391+
Because the Promise spec indicates that all promises must tick the event loop,
392+
keep in mind that any stubbing configured with `thenResolve` or `thenReject`
393+
must be managed as an asynchronous test (consult your test framework's
394+
documentation if you're not sure).
395+
396+
#### `td.when().thenCallback()`
397+
398+
**`td.when(__rehearsal__[, options]).thenCallback('some value'[,other,
399+
args])`**
400+
401+
The `thenCallback()` stubbing will assume that the rehearsed invocation has an
402+
additional final argument that takes a function. When invoked by the subject,
403+
testdouble.js will invoke that function and pass whatever arguments were sent to
404+
`thenCallback()`.
405+
406+
To illustrate, consider this stubbing:
407+
408+
```js
409+
const readFile = td.replace('../src/read-file')
410+
td.when(readFile('my-secret-doc.txt')).thenCallback(null, 'secrets!')
411+
```
412+
413+
Then, the subject might invoke readFile and pass an anonymous function:
414+
415+
```js
416+
readFile('my-secret-doc.txt', function (er, contents) {
417+
console.log(contents) // will print 'secrets!'
418+
})
419+
```
420+
421+
If the callback isn't in the final position, or if the test double also needs to
422+
return something, it can be configured using the
423+
[td.callback](/docs/5-stubbing-results.md#callback-apis-with-a-callback-argument-at-an-arbitrary-position)
424+
argument matcher.
425+
426+
#### `td.when().thenThrow()`
427+
428+
**`td.when(__rehearsal__[, options]).thenThrow(someError)`**
429+
430+
The `thenThrow()` function does exactly what it says on the tin. Once this
431+
stubbing is configured, any matching invocations will throw the specified error.
432+
433+
Note that because "rehearsal" calls invoke the test double function, that it's
434+
possible to configure `thenThrow` and then find that subsequent stubbings or
435+
verifications can't be configured without also `catch`'ing the error. This ought
436+
to be a rarely encountered edge case.
437+
438+
#### `td.when().thenDo()`
439+
440+
**`td.when(__rehearsal__[, options]).thenDo(function (arg1, arg2) {})`**
441+
442+
For everything else, there is `thenDo()`. `thenDo` takes a function which, for
443+
matching rehearsals, testdouble.js will invoke and forward along all arguments
444+
passed as well as bind the `this` context the test double function was invoked
445+
with. This callback is useful for covering tricky cases not handled elsewhere,
446+
and may be a useful extension point for building on top of the library's
447+
stubbing capabilities.
448+
449+
### `td.verify()` for verifying interactions
450+
451+
**`td.verify(__demonstration__[, options])`**
452+
453+
If you've learned how to stub responses with `td.when()` then you already know
454+
how to verify an invocation took place with `td.verify()`! We've gone out of our
455+
way to make the two as symmetrical as possible. You'll find that they have
456+
matching function signatures, support the same argument matchers, and take the
457+
same options.
458+
459+
The difference, then, is their purpose. While stubbings are meant to facilitate
460+
some behavior we want to exercise in our subject, verifications are meant to
461+
ensure a dependency was called in a particular expected way. Since `td.verify()`
462+
is an assertion step, it goes [at the
463+
end](https://github.com/testdouble/contributing-tests/wiki/Arrange-Act-Assert)
464+
of our test after we've invoked the subject under test.
465+
466+
A trivial example might be:
467+
468+
```js
469+
module.exports = function shouldSaveThings () {
470+
const save = td.replace('../src/save')
471+
const subject = require('../src/index')
472+
473+
subject({name: 'dataz', data: '010101'})
474+
475+
td.verify(save('dataz', '010101'))
476+
}
477+
```
478+
479+
The above will verify that `save` was called with the two specified arguments.
480+
Just like with `td.when()`, more complex cases can be covered with [argument
481+
matchers](/docs/6-verifying-invocations.md#relaxing-verifications-with-argument-matchers)
482+
and [configuration
483+
options](/docs/6-verifying-invocations.md#configuring-verifications).
484+
485+
A word of caution: when you verify a function was called, as opposed to what it
486+
returns, you're asserting that your code has a desired side effect. Code with
487+
lots of side effects is bad, so mocking libraries are often abused to make
488+
side-effect heavy code easier to test. In these cases, refactoring each
489+
dependency to return values instead is almost always the better design approach.
490+
Sometimes in the interest of completeness, people will attempt to verify an
491+
invocation that already satisfies a stub, but this is almost [provably
492+
unnecessary](/docs/B-frequently-asked-questions.md#why-shouldnt-i-call-both-tdwhen-and-tdverify-for-a-single-interaction-with-a-test-double).
493+
494+
### Other functions
495+
496+
For other top-level features in the testdouble.js API, consult the [docs](/docs)
497+
directory:
498+
499+
* [td.explain()](/docs/9-debugging.md#tdexplainsometestdouble)
500+
* [td.config()](/docs/C-configuration.md#tdconfig)
501+
* [td.reset()](/docs/1-installation.md#resetting-state-between-test-runs)
502+
* [td.matchers](/docs/5-stubbing-results.md#loosening-stubbings-with-argument-matchers)
503+
(and [custom matchers](/docs/8-custom-matchers.md#custom-argument-matchers))
504+

0 commit comments

Comments
 (0)