1 | # testdouble.js (AKA td.js)
|
2 |
|
3 | [![npmjs](https://img.shields.io/badge/npm-testdouble-red.svg)](https://www.npmjs.com/package/testdouble)
|
4 | [![unpkg](https://img.shields.io/badge/unpkg-download-blue.svg)](https://unpkg.com/testdouble/dist/)
|
5 |
|
6 | Welcome! Are you writing JavaScript tests and in the market for a mocking
|
7 | library to fake out real things for you? testdouble.js is an opinionated,
|
8 | carefully-designed test double library maintained by, oddly enough, a software
|
9 | agency that's also named [Test Double](http://testdouble.com). (The term "test
|
10 | double" was coined by Gerard Meszaros in his book [xUnit Test
|
11 | Patterns](http://xunitpatterns.com/Test%20Double.html).)
|
12 |
|
13 | If you practice test-driven development, testdouble.js was designed to promote
|
14 | terse, clear, and easy-to-understand tests. There's an awful lot to cover, so
|
15 | please take some time and enjoy our documentation, which is designed to show you
|
16 | how to make the most out of test doubles in your tests.
|
17 |
|
18 | This library was designed to work for both Node.js and browser interpeters. It's
|
19 | also test-framework agnostic, so you can plop it into a codebase using Jasmine,
|
20 | Mocha, Tape, Jest, or our own
|
21 | [teenytest](https://github.com/testdouble/teenytest).
|
22 |
|
23 | ## Install
|
24 |
|
25 | ```
|
26 | $ npm install -D testdouble
|
27 | ```
|
28 |
|
29 | If you just want to fetch the browser distribution, you can also curl it from
|
30 | [unpkg](https://unpkg.com/testdouble/dist/).
|
31 |
|
32 | We recommend requiring the library in a test helper and setting it globally for
|
33 | convenience to the shorthand `td`:
|
34 |
|
35 | ```js
|
36 | // ES import syntax
|
37 | import * as td from 'testdouble'
|
38 |
|
39 | // CommonJS modules (e.g. Node.js)
|
40 | globalThis.td = require('testdouble')
|
41 |
|
42 | // Global set in our browser distribution
|
43 | window.td
|
44 | ```
|
45 |
|
46 | (You may need to configure your linter to ignore the `td` global.
|
47 | Instructions:
|
48 | [eslint](https://eslint.org/docs/user-guide/configuring#specifying-globals),
|
49 | [standard](https://github.com/standard/standard/#i-use-a-library-that-pollutes-the-global-namespace-how-do-i-prevent-variable-is-not-defined-errors).)
|
50 |
|
51 | If you're using testdouble.js in conjunction with another test framework, you
|
52 | may also want to check out one of these extensions:
|
53 |
|
54 | * [testdouble-jest](https://github.com/testdouble/testdouble-jest)
|
55 | * [testdouble-chai](https://github.com/basecase/testdouble-chai)
|
56 | * [testdouble-jasmine](https://github.com/BrianGenisio/testdouble-jasmine)
|
57 | * [testdouble-qunit](https://github.com/alexlafroscia/testdouble-qunit/tree/master/packages/testdouble-qunit)
|
58 | * [testdouble-vitest](https://github.com/mcous/testdouble-vitest)
|
59 |
|
60 | ## Getting started
|
61 |
|
62 | Mocking libraries are more often abused than used effectively, so figuring out
|
63 | how to document a mocking library so as to only encourage healthy uses has
|
64 | proven to be a real challenge. Here are a few paths we've prepared for getting
|
65 | started with testdouble.js:
|
66 |
|
67 | * The [API section of this README](#api) so you can get started stubbing and
|
68 | verifying right away
|
69 | * A [20-minute
|
70 | video](http://blog.testdouble.com/posts/2016-06-05-happier-tdd-with-testdouble-js)
|
71 | overview of the library, its goals, and basic usage
|
72 | * A [comparison between testdouble.js and
|
73 | Sinon.js](http://blog.testdouble.com/posts/2016-03-13-testdouble-vs-sinon.html),
|
74 | in case you've already got experience working with Sinon and you're looking
|
75 | for a high-level overview of how they differ
|
76 | * The full testdouble.js [documentation](/docs), which describes at length how
|
77 | to (and how not to) take advantage of the various features of testdouble.js.
|
78 | Its outline is in [docs/README.md](/docs#readme)
|
79 |
|
80 | Of course, if you're unsure of how to approach writing an isolated test with
|
81 | testdouble.js, we welcome you to [open an issue on GitHub to ask a
|
82 | question](https://github.com/testdouble/testdouble.js/issues/new).
|
83 |
|
84 | ## API
|
85 |
|
86 | ### `td.replace()` and `td.replaceEsm()` for replacing dependencies
|
87 |
|
88 | The first thing a test double library needs to do is give you a way to replace
|
89 | the production dependencies of your [subject under
|
90 | test](https://github.com/testdouble/contributing-tests/wiki/Subject) with fake
|
91 | ones controlled by your test.
|
92 |
|
93 | We provide a top-level function called `td.replace()` that operates in two
|
94 | different modes: CommonJS module replacement and object-property replacement.
|
95 | Both modes will, by default, perform a deep clone of the real dependency which
|
96 | replaces all functions it encounters with fake test double functions which can,
|
97 | in turn, be configured by your test to either stub responses or assert
|
98 | invocations.
|
99 |
|
100 | For ES modules, you should use `td.replaceEsm()`. More details
|
101 | [here](docs/7-replacing-dependencies.md#how-module-replacement-works-for-es-modules-using-import).
|
102 |
|
103 | #### Module replacement with Node.js
|
104 |
|
105 | **`td.replace('../path/to/module'[, customReplacement])`**
|
106 |
|
107 | If you're using Node.js and don't mind using the CommonJS `require()` function
|
108 | in your tests (you can still use `import`/`export` in your production code,
|
109 | assuming you're compiling it down for consumption by your tests), testdouble.js
|
110 | uses a library we wrote called [quibble](https://github.com/testdouble/quibble)
|
111 | to monkey-patch `require()` so that your subject will automatically receive your
|
112 | faked dependencies simply by requiring them. This approach may be familiar if you've used something like
|
113 | [proxyquire](https://github.com/thlorenz/proxyquire), but our focus was to
|
114 | enable an even more minimal test setup.
|
115 |
|
116 | Here's an example of using `td.replace()` in a Node.js test's setup:
|
117 |
|
118 | ```js
|
119 | let loadsPurchases, generatesInvoice, sendsInvoice, subject
|
120 | module.exports = {
|
121 | beforeEach: () => {
|
122 | loadsPurchases = td.replace('../src/loads-purchases')
|
123 | generatesInvoice = td.replace('../src/generates-invoice')
|
124 | sendsInvoice = td.replace('../src/sends-invoice')
|
125 | subject = require('../src/index')
|
126 | }
|
127 | //…
|
128 | afterEach: function () { td.reset() }
|
129 | }
|
130 | ```
|
131 |
|
132 | In the above example, at the point when `src/index` is required, the module
|
133 | cache will be bypassed as `index` is loaded. If `index` goes on to subsequently
|
134 | require any of the `td.replace()`'d dependencies, it will receive a reference to
|
135 | the same fake dependencies that were returned to the test.
|
136 |
|
137 | Because `td.replace()` first loads the actual file, it will do its best to
|
138 | return a fake that is shaped just like the real thing. That means that if
|
139 | `loads-purchases` exports a function, a test double function will be created and
|
140 | returned. If `generates-invoice` exports a constructor, a constructor test
|
141 | double will be returned, complete with test doubles for all of the original's
|
142 | static functions and instance methods. If `sends-invoice` exports a plain
|
143 | object of function properties, an object will be returned with test double
|
144 | functions in place of the originals' function properties. In every case, any
|
145 | non-function properties will be deep-cloned.
|
146 |
|
147 | There are a few important things to keep in mind about replacing Node.js modules
|
148 | using `td.replace()`:
|
149 |
|
150 | * The test must `td.replace()` and `require()` everything in a before-each hook,
|
151 | in order to bypass the Node.js module cache and to avoid pollution between
|
152 | tests
|
153 | * Any relative paths passed to `td.replace()` are relative *from the test to the
|
154 | dependency*. This runs counter to how some other tools do it, but we feel it
|
155 | makes more sense
|
156 | * The test suite (usually in a global after-each hook) must call `td.reset()` to
|
157 | ensure the real `require()` function and dependency modules are restored after
|
158 | each test case.
|
159 |
|
160 | ##### Default exports with ES modules
|
161 |
|
162 | If your modules are written in the ES module syntax and they specify default
|
163 | exports (e.g. `export default function loadsPurchases()`), but are actually
|
164 | transpiled to CommonJS, just remember that you'll need to reference `.default`
|
165 | when translating to the CJS module format.
|
166 |
|
167 | That means instead of this:
|
168 |
|
169 | ```js
|
170 | loadsPurchases = td.replace('../src/loads-purchases')
|
171 | ```
|
172 |
|
173 | You probably want to assign the fake like this:
|
174 |
|
175 | ```js
|
176 | loadsPurchases = td.replace('../src/loads-purchases').default
|
177 | ```
|
178 |
|
179 | #### Property replacement
|
180 |
|
181 | **`td.replace(containingObject, nameOfPropertyToReplace[, customReplacement])`**
|
182 |
|
183 | If you're running tests outside Node.js or otherwise injecting dependencies
|
184 | manually (or with a DI tool like
|
185 | [dependable](https://github.com/testdouble/dependable)), then you may still use
|
186 | `td.replace` to automatically replace things if they're referenceable as
|
187 | properties on an object.
|
188 |
|
189 | To illustrate, suppose our subject depends on `app.signup` below:
|
190 |
|
191 | ``` js
|
192 | app.signup = {
|
193 | onSubmit: function () {},
|
194 | onCancel: function () {}
|
195 | }
|
196 | ```
|
197 |
|
198 | If our goal is to replace `app.signup` during a test of `app.user.create()`,
|
199 | our test setup might look like this:
|
200 |
|
201 | ```js
|
202 | let signup, subject
|
203 | module.exports = {
|
204 | beforeEach: function () {
|
205 | signup = td.replace(app, 'signup')
|
206 | subject = app.user
|
207 | }
|
208 | // …
|
209 | afterEach: function () { td.reset() }
|
210 | }
|
211 | ```
|
212 |
|
213 | `td.replace()` will always return the newly-created fake imitation, even though
|
214 | in this case it's obviously still referenceable by the test and subject alike
|
215 | with `app.signup`. If we had wanted to only replace the `onCancel` function for
|
216 | whatever reason (though in this case, that would smell like a [partial
|
217 | mock](https://github.com/testdouble/contributing-tests/wiki/Partial-Mock)), we
|
218 | could have called `td.replace(app.signup, 'onCancel')`, instead.
|
219 |
|
220 | Remember to call `td.reset()` in an after-each hook (preferably globally so one
|
221 | doesn't have to remember to do so in each and every test) so that testdouble.js
|
222 | can replace the original. This is crucial to avoiding hard-to-debug test
|
223 | pollution!
|
224 |
|
225 | #### Specifying a custom replacement
|
226 |
|
227 | The library's [imitation
|
228 | feature](https://github.com/testdouble/testdouble.js/blob/main/src/imitate/index.js)
|
229 | is pretty sophisticated, but it's not perfect. It's also going to be pretty slow
|
230 | on large, complex objects. If you'd like to specify exactly what to replace a
|
231 | real dependency with, you can do so in either of the above modes by providing a
|
232 | final optional argument.
|
233 |
|
234 | When replacing a Node.js module:
|
235 |
|
236 | ```js
|
237 | generatesInvoice = td.replace('../generates-invoice', {
|
238 | generate: td.func('a generate function'),
|
239 | name: 'fake invoices'
|
240 | })
|
241 | ```
|
242 |
|
243 | When replacing a property:
|
244 |
|
245 | ```js
|
246 | signup = td.replace(app, 'signup', {
|
247 | onSubmit: td.func('fake submit handler'),
|
248 | onCancel: function () { throw Error('do not call me') }
|
249 | })
|
250 | ```
|
251 |
|
252 | ### `td.func()`, `td.object()`, `td.constructor()`, `td.instance()` and `td.imitate()` to create test doubles
|
253 |
|
254 | `td.replace()`'s imitation and injection convenience is great when your
|
255 | project's build configuration allows for it, but in many cases you'll want or
|
256 | need the control to create fake things directly. Each creation function can
|
257 | either imitate a real thing or be specified by passing a bit of configuration.
|
258 |
|
259 | Each test double creation function is very flexible and can take a variety of
|
260 | inputs. What gets returned generally depends on the number and type of configuration
|
261 | parameters passed in, so we'll highlight each supported usage separately with an
|
262 | example invocation:
|
263 |
|
264 | #### `td.func()`
|
265 |
|
266 | The `td.func()` function (also available as `td.function()`) returns a test
|
267 | double function and can be called in three modes:
|
268 |
|
269 | * **`td.func(someRealFunction)`** - returns a test double function of the same
|
270 | `name`, including a deep
|
271 | [imitation](https://github.com/testdouble/testdouble.js/blob/main/src/imitate/index.js)
|
272 | of all of its custom properties
|
273 | * **`td.func()`** - returns an anonymous test double function that can be used
|
274 | for stubbing and verifying any calls against it, but whose error messages and
|
275 | debugging output won't have a name to trace back to it
|
276 | * **`td.func('some name')`** - returns a test double function named `'some
|
277 | name'`, which will appear in any error messages as well as the debug info
|
278 | returned by passing the returned test double into
|
279 | [td.explain()](/docs/9-debugging.md#tdexplainsometestdouble)
|
280 | * **`td.func<Type>()`** - returns a test double function imitating the passed type.
|
281 | Examples and more details can be found in [using with TypeScript](/docs/10-using-with-typescript.md)
|
282 |
|
283 | #### `td.object()`
|
284 |
|
285 | The `td.object()` function returns an object containing test double functions,
|
286 | and supports three types of invocations:
|
287 |
|
288 | * **`td.object(realObject)`** - returns a deep
|
289 | [imitation](https://github.com/testdouble/testdouble.js/blob/main/src/imitate/index.js)
|
290 | of the passed object, where each function is replaced with a test double function
|
291 | named for the property path (e.g. If `realObject.invoices.send()` was a
|
292 | function, the returned object would have property `invoices.send` set to a
|
293 | test double named `'.invoices.send'`)
|
294 | * **`td.object(['add', 'subtract'])`** - returns a plain JavaScript object
|
295 | containing two properties `add` and `subtract` that are both assigned to test
|
296 | double functions named `'.add'` and `'.subtract'`, respectively
|
297 | * **`td.object('a Person'[, {excludeMethods: ['then']})`** - when passed with no
|
298 | args or with a string name as the first argument, returns an [ES
|
299 | Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy).
|
300 | The proxy will automatically intercept any call made to it and shunt in a test
|
301 | double that can be used for stubbing or verification. More details can be
|
302 | found in [our full docs](/docs/4-creating-test-doubles.md#objectobjectname)
|
303 | * **`td.object<Interface>()`** - returns an object with methods exposed as test doubles
|
304 | that are typed according to the passed interface. Examples and more details can be found in
|
305 | [using with TypeScript](/docs/10-using-with-typescript.md)
|
306 |
|
307 | #### `td.constructor()`
|
308 |
|
309 | If your code depends on ES classes or functions intended to be called with
|
310 | `new`, then the `td.constructor()` function can replace those dependencies as
|
311 | well.
|
312 |
|
313 | * **`td.constructor(RealConstructor)`** - returns a constructor whose calls can
|
314 | be verified and whose static and `prototype` functions have all been replaced
|
315 | with test double functions using the same
|
316 | [imitation](https://github.com/testdouble/testdouble.js/blob/main/src/imitate/index.js)
|
317 | mechanism as `td.func(realFunction)` and `td.object(realObject)`
|
318 | * **`td.constructor(['select', 'save'])`** - returns a constructor with `select`
|
319 | and `save` properties on its `prototype` object set to test double functions
|
320 | named `'#select'` and `'#save'`, respectively
|
321 |
|
322 | When replacing a constructor, typically the test will configure stubbing &
|
323 | verification by directly addressing its prototype functions. To illustrate, that
|
324 | means in your test you might write:
|
325 |
|
326 | ```js
|
327 | const FakeConstructor = td.constructor(RealConstructor)
|
328 | td.when(FakeConstructor.prototype.doStuff()).thenReturn('ok')
|
329 |
|
330 | subject(FakeConstructor)
|
331 | ```
|
332 |
|
333 | So that in your production code you can:
|
334 |
|
335 | ```js
|
336 | const subject = function (SomeConstructor) {
|
337 | const thing = new SomeConstructor()
|
338 | return thing.doStuff() // returns "ok"
|
339 | }
|
340 | ```
|
341 |
|
342 | #### `td.instance()`
|
343 |
|
344 | As a shorthand convenience, `td.instance()` function will call
|
345 | `td.constructor()` and return a `new` instance of the fake constructor function
|
346 | it returns.
|
347 |
|
348 | The following code snippets are functionally equivalent:
|
349 |
|
350 | ```js
|
351 | const fakeObject = td.instance(RealConstructor)
|
352 | ```
|
353 |
|
354 | ```js
|
355 | const FakeConstructor = td.constructor(RealConstructor)
|
356 | const fakeObject = new FakeConstructor()
|
357 | ```
|
358 |
|
359 | #### `td.imitate()`
|
360 |
|
361 | **`td.imitate(realThing[, name])`**
|
362 |
|
363 | If you know you want to imitate something, but don't know (or care) whether it's
|
364 | a function, object, or constructor, you can also just pass it to `td.imitate()`
|
365 | with an optional name parameter.
|
366 |
|
367 | ### `td.when()` for stubbing responses
|
368 |
|
369 | **`td.when(__rehearsal__[, options])`**
|
370 |
|
371 | Once you have your subject's dependencies replaced with test double functions,
|
372 | you'll want to be able to stub return values (and other sorts of responses)
|
373 | when the subject invokes the test double in the way that the test expects.
|
374 |
|
375 | To make stubbing configuration easy to read and grep, `td.when()`'s first
|
376 | argument isn't an argument at all, but rather a placeholder to demonstrate the
|
377 | way you're expecting the test double to be invoked by the subject, like so:
|
378 |
|
379 | ```js
|
380 | const increment = td.func()
|
381 | td.when(increment(5)).thenReturn(6)
|
382 | ```
|
383 |
|
384 | We would say that `increment(5)` is "rehearsing the invocation". Note that by
|
385 | default, a stubbing is only satisfied when the subject calls the test double
|
386 | exactly as it was rehearsed. This can be customized with [argument
|
387 | matchers](/docs/5-stubbing-results.md#loosening-stubbings-with-argument-matchers),
|
388 | which allow for rehearsals that do things like
|
389 | `increment(td.matchers.isA(Number))` or `save(td.matchers.contains({age: 21}))`.
|
390 |
|
391 | Also note that, `td.when()` takes an [optional configuration
|
392 | object](/docs/5-stubbing-results.md#configuring-stubbings) as a second
|
393 | parameter, which enables advanced usage like ignoring extraneous arguments and
|
394 | limiting the number of times a stubbing can be satisfied.
|
395 |
|
396 | Calling `td.when()` returns a number of functions that allow you to specify your
|
397 | desired outcome when the test double is invoked as demonstrated by your
|
398 | rehearsal. We'll begin with the most common of these: `thenReturn`.
|
399 |
|
400 | #### `td.when().thenReturn()`
|
401 |
|
402 | **`td.when(__rehearsal__[, options]).thenReturn('some value'[, more, values])`**
|
403 |
|
404 | The simplest example is when you want to return a specific value in exchange for
|
405 | a known argument, like so:
|
406 |
|
407 | ```js
|
408 | const loadsPurchases = td.replace('../src/loads-purchases')
|
409 | td.when(loadsPurchases(2018, 8)).thenReturn(['a purchase', 'another'])
|
410 | ```
|
411 |
|
412 | Then, in the hands of your subject under test:
|
413 |
|
414 | ```js
|
415 | loadsPurchases(2018, 8) // returns `['a purchase', 'another']`
|
416 | loadsPurchases(2018, 7) // returns undefined, since no stubbing was satisfied
|
417 | ```
|
418 |
|
419 | If you're not used to stubbing, it may seem contrived to think a test will know
|
420 | exactly what argument to pass in and expect back from a dependency, but in an
|
421 | isolated unit test this is not only feasible but entirely normal and expected!
|
422 | Doing so helps the author ensure the test remains minimal and obvious to
|
423 | future readers.
|
424 |
|
425 | Note as well that subsequent matching invocations can be stubbed by passing
|
426 | additional arguments to `thenReturn()`, like this:
|
427 |
|
428 | ```js
|
429 | const hitCounter = td.func()
|
430 | td.when(hitCounter()).thenReturn(1, 2, 3, 4)
|
431 |
|
432 | hitCounter() // 1
|
433 | hitCounter() // 2
|
434 | hitCounter() // 3
|
435 | hitCounter() // 4
|
436 | hitCounter() // 4
|
437 | ```
|
438 |
|
439 | #### `td.when().thenResolve()` and `td.when().thenReject()`
|
440 |
|
441 | **`td.when(__rehearsal__[, options]).thenResolve('some value'[, more, values])`**
|
442 |
|
443 | **`td.when(__rehearsal__[, options]).thenReject('some value'[, more, values])`**
|
444 |
|
445 | The `thenResolve()` and `thenReject()` stubbings will take whatever value is
|
446 | passed to them and wrap it in an immediately resolved or rejected promise,
|
447 | respectively. By default testdouble.js will use whatever `Promise` is globally
|
448 | defined, but you can specify your own like this:
|
449 |
|
450 | ```js
|
451 | td.config({promiseConstructor: require('bluebird')})`
|
452 | ```
|
453 |
|
454 | Because the Promise spec indicates that all promises must tick the event loop,
|
455 | keep in mind that any stubbing configured with `thenResolve` or `thenReject`
|
456 | must be managed as an asynchronous test (consult your test framework's
|
457 | documentation if you're not sure).
|
458 |
|
459 | #### `td.when().thenCallback()`
|
460 |
|
461 | **`td.when(__rehearsal__[, options]).thenCallback('some value'[,other,
|
462 | args])`**
|
463 |
|
464 | The `thenCallback()` stubbing will assume that the rehearsed invocation has an
|
465 | additional final argument that takes a callback function. When this stubbing is
|
466 | satisfied, testdouble.js will invoke that callback function and pass in whatever
|
467 | arguments were sent to `thenCallback()`.
|
468 |
|
469 | To illustrate, consider this stubbing:
|
470 |
|
471 | ```js
|
472 | const readFile = td.replace('../src/read-file')
|
473 | td.when(readFile('my-secret-doc.txt')).thenCallback(null, 'secrets!')
|
474 | ```
|
475 |
|
476 | Then, the subject might invoke readFile and pass an anonymous function:
|
477 |
|
478 | ```js
|
479 | readFile('my-secret-doc.txt', function (err, contents) {
|
480 | console.log(contents) // will print 'secrets!'
|
481 | })
|
482 | ```
|
483 |
|
484 | If the callback isn't in the final position, or if the test double also needs to
|
485 | return something, callbacks can be configured using the
|
486 | [td.callback](/docs/5-stubbing-results.md#callback-apis-with-a-callback-argument-at-an-arbitrary-position)
|
487 | argument matcher.
|
488 |
|
489 | On one hand, `thenCallback()` can be a great way to write fast and clear
|
490 | synchronous isolated unit tests of production code that's actually asynchronous.
|
491 | On the other hand, if it's necessary to verify the subject behaves correctly
|
492 | over multiple ticks of the event loop, you can control this with the [`defer`
|
493 | and `delay` options](/docs/5-stubbing-results.md#defer).
|
494 |
|
495 | #### `td.when().thenThrow()`
|
496 |
|
497 | **`td.when(__rehearsal__[, options]).thenThrow(new Error('boom'))`**
|
498 |
|
499 | The `thenThrow()` function does exactly what it says on the tin. Once this
|
500 | stubbing is configured, any matching invocations will throw the specified error.
|
501 |
|
502 | Note that because rehearsal calls invoke the test double function, it's possible
|
503 | to configure a `thenThrow` stubbing and then accidentally trigger it when you
|
504 | attempt to configure subsequent stubbings or verifications. In these cases,
|
505 | you'll need to work around it by re-ordering your configurations or `catch`'ing
|
506 | the error.
|
507 |
|
508 | #### `td.when().thenDo()`
|
509 |
|
510 | **`td.when(__rehearsal__[, options]).thenDo(function (arg1, arg2) {})`**
|
511 |
|
512 | For everything else, there is `thenDo()`. `thenDo` takes a function which will
|
513 | be invoked whenever satisfied with all the arguments and bound to the same
|
514 | `this` context that the test double function was actually invoked with. Whatever
|
515 | your `thenDo` function returns will be returned by the test double when the
|
516 | stubbing is satisfied. This configuration is useful for covering tricky cases
|
517 | not handled elsewhere, and may be a potential extension point for building on
|
518 | top of the library's stubbing capabilities.
|
519 |
|
520 | ### `td.verify()` for verifying interactions
|
521 |
|
522 | **`td.verify(__demonstration__[, options])`**
|
523 |
|
524 | If you've learned how to stub responses with `td.when()` then you already know
|
525 | how to verify an invocation took place with `td.verify()`! We've gone out of our
|
526 | way to make the two as symmetrical as possible. You'll find that they have
|
527 | matching function signatures, support the same argument matchers, and take the
|
528 | same options.
|
529 |
|
530 | The difference, then, is their purpose. While stubbings are meant to facilitate
|
531 | some behavior we want to exercise in our subject, verifications are meant to
|
532 | ensure a dependency was called in a particular expected way. Since `td.verify()`
|
533 | is an assertion step, it goes [at the
|
534 | end](https://github.com/testdouble/contributing-tests/wiki/Arrange-Act-Assert)
|
535 | of our test after we've invoked the subject under test.
|
536 |
|
537 | A trivial example might be:
|
538 |
|
539 | ```js
|
540 | module.exports = function shouldSaveThings () {
|
541 | const save = td.replace('../src/save')
|
542 | const subject = require('../src/index')
|
543 |
|
544 | subject({name: 'dataz', data: '010101'})
|
545 |
|
546 | td.verify(save('dataz', '010101'))
|
547 | }
|
548 | ```
|
549 |
|
550 | The above will verify that `save` was called with the two specified arguments.
|
551 | If the verification fails (say it passed `'010100'` instead), testdouble.js will
|
552 | throw a nice long error message to explain how the test double function was
|
553 | actually called, hopefully helping you spot the error.
|
554 |
|
555 | Just like with `td.when()`, more complex cases can be covered with [argument
|
556 | matchers](/docs/6-verifying-invocations.md#relaxing-verifications-with-argument-matchers)
|
557 | and [configuration
|
558 | options](/docs/6-verifying-invocations.md#configuring-verifications).
|
559 |
|
560 | A word of caution: `td.verify()` should be needed only sparingly. When you
|
561 | verify a function was called (as opposed to relying on what it returns) you're
|
562 | asserting that your subject has a side effect. Code with lots of side effects is
|
563 | bad, so mocking libraries are often abused to make side-effect heavy code easier
|
564 | to proliferate. In these cases, refactoring each dependency to return values
|
565 | instead is almost always the better design approach. A separate test smell with
|
566 | verifying calls is that sometimes—perhaps in the interest of maximal
|
567 | completeness—a test will verify an invocation that already satisfied a stubbing,
|
568 | but this is almost [provably
|
569 | unnecessary](/docs/B-frequently-asked-questions.md#why-shouldnt-i-call-both-tdwhen-and-tdverify-for-a-single-interaction-with-a-test-double).
|
570 |
|
571 | ### `td.listReplacedModules()` for listing the modules that were replaced
|
572 |
|
573 | **`td.listReplacedModules()`**
|
574 |
|
575 |
|
576 | Use `td.listReplacedModules()` to list the modules that are replaced. This function will return an array of the modules that are
|
577 | currently being replaced via `td.replace()` or `td.replaceEsm()`.
|
578 |
|
579 | The list is in no particular order, and returns the full path to the module that was replaced.
|
580 | The path is returned as a `file:` URL as is customary in ESM (this is true even if the
|
581 | replaced module was CJS).
|
582 |
|
583 | For example, if you do this:
|
584 |
|
585 | ```js
|
586 | td.replace('../src/save')
|
587 | ```
|
588 |
|
589 | Then
|
590 |
|
591 | ```js
|
592 | td.listReplacedModules()
|
593 | ```
|
594 |
|
595 | will return something like:
|
596 |
|
597 | ```js
|
598 | ['file:///users/example/code/foo/src/save.js']
|
599 | ```
|
600 |
|
601 | ### Other functions
|
602 |
|
603 | For other top-level features in the testdouble.js API, consult the [docs](/docs)
|
604 | directory:
|
605 |
|
606 | * [td.explain()](/docs/9-debugging.md#tdexplainsometestdouble) - for help
|
607 | debugging and introspecting test doubles
|
608 | * [td.config()](/docs/C-configuration.md#tdconfig) - for changing globally
|
609 | configurable options
|
610 | * [td.reset()](/docs/1-installation.md#resetting-state-between-test-runs) - for
|
611 | resetting testdouble.js state between tests
|
612 | * [td.matchers](/docs/5-stubbing-results.md#loosening-stubbings-with-argument-matchers)
|
613 | and [custom matchers](/docs/8-custom-matchers.md#custom-argument-matchers) for
|
614 | configuring more advanced stubbings and verifications
|