@@ -14,19 +14,19 @@ terse, clear, and easy-to-understand tests. There's an awful lot to cover, so
14
14
please take some time and enjoy our documentation, which is designed to show you
15
15
how to make the most out of test doubles in your tests.
16
16
17
- This library was designed to work for both Node.js and browser interpeters and
18
- to be test-framework agnostic, so you can plop it into a codebase using Jasmine,
17
+ This library was designed to work for both Node.js and browser interpeters. It's
18
+ also test-framework agnostic, so you can plop it into a codebase using Jasmine,
19
19
Mocha, Tape, Jest, or our own
20
20
[ teenytest] ( https://github.com/testdouble/teenytest ) .
21
21
22
22
## Install
23
23
24
- ``` js
25
- npm install - D testdouble
24
+ ```
25
+ $ npm install -D testdouble
26
26
```
27
27
28
- If you just want to fetch the browser distribution, you can also get it from
29
- [ unpkg] ( https://unpkg.com/testdouble/dist/ )
28
+ If you just want to fetch the browser distribution, you can also curl it from
29
+ [ unpkg] ( https://unpkg.com/testdouble/dist/ ) .
30
30
31
31
We recommend requiring the library in a test helper and setting it globally for
32
32
convenience to the shorthand ` td ` :
@@ -38,13 +38,13 @@ global.td = require('testdouble') // Node.js; `window.td` for browsers
38
38
(You may need to declare the global in order to make your linter handy.
39
39
Instructions:
40
40
[ eslint] ( https://eslint.org/docs/user-guide/configuring#specifying-globals ) ,
41
- [ Standard ] ( https://github.com/standard/standard/#i-use-a-library-that-pollutes-the-global-namespace-how-do-i-prevent-variable-is-not-defined-errors ) .)
41
+ [ standard ] ( https://github.com/standard/standard/#i-use-a-library-that-pollutes-the-global-namespace-how-do-i-prevent-variable-is-not-defined-errors ) .)
42
42
43
43
## Getting started
44
44
45
45
Mocking libraries are more often abused than used effectively, so figuring out
46
- how to document a mocking library that is designed to encourage only productive
47
- use has been a real challenge. Here are a few paths to getting started:
46
+ how to document a mocking library in such a way as to only encourage healthy
47
+ use has proven to be a real challenge. Here are a few paths to getting started:
48
48
49
49
* The [ API section of this README] ( #api ) to get an at-a-glance view of
50
50
the API so you can get started stubbing and verifying right away
@@ -60,6 +60,10 @@ use has been a real challenge. Here are a few paths to getting started:
60
60
the various faetures of testdouble.js. Its outline is at the [ bottom of this
61
61
README] ( #docs )
62
62
63
+ Of course, if you're unsure of how to approach writing an isolated test with
64
+ testdouble.js, we welcome you to [ open a issue on GitHub to ask a
65
+ question] ( https://github.com/testdouble/testdouble.js/issues/new ) .
66
+
63
67
## API
64
68
65
69
### ` td.replace() ` for replacing dependencies
@@ -69,10 +73,16 @@ the production dependencies of your [subject under
69
73
test] ( https://github.com/testdouble/contributing-tests/wiki/Subject ) with fake
70
74
ones created by the library.
71
75
72
- testdouble.js provides a top-level method called ` td.replace() `
76
+ We provide a top-level method called ` td.replace() ` that operates in two
77
+ different modes: CommonJS module replacement and object-property replacement.
78
+ Both modes will, by default, perform a deep clone the real dependency but
79
+ replace all of its functions with fake test double functions that you can
80
+ configure and observe.
73
81
74
82
#### Module replacement with Node.js
75
83
84
+ ** ` td.replace('../path/to/module'[, customReplacement]) ` **
85
+
76
86
If you're using Node.js and don't mind using the CommonJS ` require ` method in
77
87
your tests (you can still use ` import ` /` export ` in your production code,
78
88
assuming you're compiling it down for consumption by your tests), testdouble.js
@@ -95,10 +105,20 @@ module.exports = {
95
105
subject = require (' ../src/index' )
96
106
}
97
107
// …
108
+ afterEach : function () { td .reset () }
98
109
}
99
110
```
100
111
101
- Things to remember about replacing Node.js modules:
112
+ In the above example, at the point when ` src/index ` is required, the module
113
+ cache will be bypassed, and if ` index ` goes on to subsequently require any of
114
+ the ` td.replace() ` 'd dependencies, it will receive a reference to the same fake
115
+ dependency returned to the test. If ` loads-purchases ` exports a function, a test
116
+ double function will be created to imitate it. If ` generates-invoice ` exports a
117
+ constructor, the constructor and all of its instance methods will also be
118
+ imitated. If ` sends-invoice ` exports a plain object of function properties, each
119
+ function will be replaced with a test double (and the other values cloned).
120
+
121
+ To repeat, important things to remember about replacing Node.js modules:
102
122
103
123
* The test must ` td.replace ` and ` require ` everything in a before-each hook,
104
124
in order to bypass Node's module cache and avoid test pollution
@@ -108,12 +128,93 @@ in order to bypass Node's module cache and avoid test pollution
108
128
* The test suite (usually in a global after-each hook) must call ` td.reset() ` to
109
129
avoid test pollution
110
130
131
+ ##### default exports with ES modules
132
+
133
+ If your production code is written in ES module syntax and specifies a default
134
+ export, just remember that you'll need to reference ` .default ` when translating
135
+ to CJS syntax. That means:
136
+
137
+ ``` js
138
+ loadsPurchases = td .replace (' ../src/loads-purchases' )
139
+ ```
140
+
141
+ Probably needs to be written as:
142
+
143
+ ``` js
144
+ loadsPurchases = td .replace (' ../src/loads-purchases' ).default
145
+ ```
146
+
111
147
#### Property replacement
112
148
149
+ ** ` td.replace(containingObject, nameOfPropertyToReplace[, customReplacement]) ` **
150
+
113
151
If you're running tests outside Node.js or otherwise injecting dependencies
114
152
manually (or with a DI tool like
115
- [ dependable] ( https://github.com/testdouble/dependable ) ), then you can still use
116
- ` td.replace ` to automatically replace and imitate
153
+ [ dependable] ( https://github.com/testdouble/dependable ) ), then you may still use
154
+ ` td.replace ` to automatically replace things if they're addressable as
155
+ properties on an object.
156
+
157
+ To illustrate, suppose our subject depends on ` app.signup ` below:
158
+
159
+ ``` js
160
+ app .signup = {
161
+ onSubmit : function () {},
162
+ onCancel : function () {}
163
+ }
164
+ ```
165
+
166
+ If our goal is to replace ` app.signup ` so during a test of `app.user.create(),
167
+ our test setup might look like this:
168
+
169
+ ``` js
170
+ let signup, subject
171
+ module .exports = {
172
+ beforeEach : function () {
173
+ signup = td .replace (app, ' signup' )
174
+ subject = app .user
175
+ }
176
+ // …
177
+ afterEach : function () { td .reset () }
178
+ }
179
+ ```
180
+
181
+ ` td.replace() ` will always return the newly-created fake imitation, even though
182
+ in this case it's obviously still referenceable by the test and subject alike
183
+ with ` app.signup ` . If we had wanted to only replace the ` onCancel ` function for
184
+ whatever reason (though in this case, that would smell like a [ partial
185
+ mock] ( https://github.com/testdouble/contributing-tests/wiki/Partial-Mock ) ), we
186
+ could have called ` td.replace(app.signup, 'onCancel) ` , instead.
187
+
188
+ Remember, calling ` td.reset() ` in an after-each hook (preferably globally so one
189
+ doesn't have to remember in each-and-every test) so that testdouble.js can
190
+ replace the original is crucial to avoiding test pollution!
191
+
192
+ #### Specifying a custom replacement
193
+
194
+ The library's [ imitation
195
+ feature] ( https://github.com/testdouble/testdouble.js/blob/updte-readme/src/imitate/index.js )
196
+ is pretty sophisticated, but it's not perfect. It's also going to be pretty slow
197
+ on large, complex objects. If you'd like to specify exactly what to replace a
198
+ real dependency with, you can do so in either of the above modes by providing a
199
+ final optional argument.
200
+
201
+ When replacing a Node.js module:
202
+
203
+ ``` js
204
+ generatesInvoice = td .replace (' ../generates-invoice' , {
205
+ generate: td .func (' a generate function' ),
206
+ name: ' fake invoices'
207
+ })
208
+ ```
209
+
210
+ When replacing a property:
211
+
212
+ ``` js
213
+ signup = td .replace (app, ' signup' , {
214
+ onSubmit: td .func (' fake submit handler' ),
215
+ onCancel : function () { throw Error (' do not call me' ) }
216
+ })
217
+ ```
117
218
118
219
### Stubbing return values for functions
119
220
0 commit comments