strong-mock 7.2.1 → 8.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +180 -118
- package/dist/errors.d.ts +1 -1
- package/dist/expectation/expectation.d.ts +5 -1
- package/dist/expectation/it.d.ts +29 -0
- package/dist/expectation/matcher.d.ts +9 -45
- package/dist/expectation/repository/expectation-repository.d.ts +17 -2
- package/dist/expectation/repository/flexible-repository.d.ts +37 -0
- package/dist/expectation/{matcher-expectation.d.ts → strong-expectation.d.ts} +4 -6
- package/dist/index.d.ts +9 -7
- package/dist/index.js +1095 -953
- package/dist/index.js.map +1 -0
- package/dist/mock/defaults.d.ts +11 -0
- package/dist/mock/map.d.ts +3 -1
- package/dist/mock/mock.d.ts +16 -17
- package/dist/mock/options.d.ts +72 -0
- package/dist/mock/stub.d.ts +12 -2
- package/dist/return/returns.d.ts +6 -6
- package/dist/verify/reset.d.ts +2 -2
- package/dist/verify/verify.d.ts +1 -1
- package/dist/when/pending-expectation.d.ts +5 -3
- package/dist/when/when.d.ts +6 -5
- package/package.json +23 -25
- package/CHANGELOG.md +0 -135
- package/dist/expectation/repository/base-repository.d.ts +0 -41
- package/dist/expectation/repository/strong-repository.d.ts +0 -10
- package/dist/expectation/repository/weak-repository.d.ts +0 -17
- package/dist/instance/instance.d.ts +0 -14
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
</div>
|
|
7
7
|
|
|
8
8
|
```typescript
|
|
9
|
-
import { mock, when
|
|
9
|
+
import { mock, when } from 'strong-mock';
|
|
10
10
|
|
|
11
11
|
interface Foo {
|
|
12
12
|
bar: (x: number) => string;
|
|
@@ -14,9 +14,9 @@ interface Foo {
|
|
|
14
14
|
|
|
15
15
|
const foo = mock<Foo>();
|
|
16
16
|
|
|
17
|
-
when(foo.bar(23)).thenReturn('I am strong!');
|
|
17
|
+
when(() => foo.bar(23)).thenReturn('I am strong!');
|
|
18
18
|
|
|
19
|
-
console.log(
|
|
19
|
+
console.log(foo.bar(23)); // 'I am strong!'
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
----
|
|
@@ -27,12 +27,12 @@ console.log(instance(foo).bar(23)); // 'I am strong!'
|
|
|
27
27
|
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
|
28
28
|
**Table of Contents**
|
|
29
29
|
|
|
30
|
-
- [Installation](#installation)
|
|
31
|
-
- [Requirements](#requirements)
|
|
32
30
|
- [Features](#features)
|
|
33
31
|
- [Type safety](#type-safety)
|
|
34
32
|
- [Useful error messages](#useful-error-messages)
|
|
35
33
|
- [Type safe argument matchers](#type-safe-argument-matchers)
|
|
34
|
+
- [Installation](#installation)
|
|
35
|
+
- [Requirements](#requirements)
|
|
36
36
|
- [API](#api)
|
|
37
37
|
- [Setting expectations](#setting-expectations)
|
|
38
38
|
- [Setting multiple expectations](#setting-multiple-expectations)
|
|
@@ -44,75 +44,85 @@ console.log(instance(foo).bar(23)); // 'I am strong!'
|
|
|
44
44
|
- [Verifying expectations](#verifying-expectations)
|
|
45
45
|
- [Resetting expectations](#resetting-expectations)
|
|
46
46
|
- [Argument matchers](#argument-matchers)
|
|
47
|
+
- [Mock options](#mock-options)
|
|
48
|
+
- [Strictness](#strictness)
|
|
49
|
+
- [Concrete matcher](#concrete-matcher)
|
|
50
|
+
- [Defaults](#defaults)
|
|
47
51
|
- [FAQ](#faq)
|
|
48
52
|
- [Why do I have to set all expectations first?](#why-do-i-have-to-set-all-expectations-first)
|
|
49
|
-
- [Can I mock an existing object/function?](#can-i-mock-an-existing-objectfunction)
|
|
53
|
+
- [Can I partially mock an existing object/function?](#can-i-partially-mock-an-existing-objectfunction)
|
|
50
54
|
- [How do I set expectations on setters?](#how-do-i-set-expectations-on-setters)
|
|
51
55
|
- [Why do I have to set a return value even if it's `undefined`?](#why-do-i-have-to-set-a-return-value-even-if-its-undefined)
|
|
52
56
|
- [How do I provide a function for the mock to call?](#how-do-i-provide-a-function-for-the-mock-to-call)
|
|
53
|
-
- [
|
|
54
|
-
- [
|
|
57
|
+
- [Can I spread/enumerate a mock?](#can-i-spreadenumerate-a-mock)
|
|
58
|
+
- [How can I ignore `undefined` keys when setting expectations on objects?](#how-can-i-ignore-undefined-keys-when-setting-expectations-on-objects)
|
|
55
59
|
|
|
56
60
|
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
|
57
61
|
|
|
58
|
-
## Installation
|
|
59
|
-
|
|
60
|
-
```
|
|
61
|
-
npm i -D strong-mock
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
yarn add -D strong-mock
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Requirements
|
|
69
|
-
|
|
70
|
-
strong-mock requires an environment that supports the [ES6 Proxy object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). This is necessary to create dynamic mocks from types because TypeScript does not support reflection i.e. exposing the type info at runtime.
|
|
71
|
-
|
|
72
62
|
## Features
|
|
73
63
|
|
|
74
64
|
### Type safety
|
|
75
65
|
|
|
76
66
|
The created mock matches the mocked type so all expectations are type safe. Moreover, refactorings in an IDE will also cover your expectations.
|
|
77
67
|
|
|
78
|
-

|
|
79
69
|
|
|
80
70
|
### Useful error messages
|
|
81
71
|
|
|
82
72
|
Error messages include the property that has been accessed, any arguments passed to it and any remaining unmet expectations.
|
|
83
73
|
|
|
84
|
-
|
|
74
|
+
```typescript
|
|
75
|
+
import { mock, when } from 'strong-mock';
|
|
76
|
+
|
|
77
|
+
const fn = mock<(a: number, b: number, c: number) => number>();
|
|
78
|
+
|
|
79
|
+
when(() => fn(1, 2, 3)).thenReturn(42);
|
|
80
|
+
|
|
81
|
+
fn(4, 5, 6);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+

|
|
85
85
|
|
|
86
86
|
### Type safe argument matchers
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
Optional argument matchers allow you to create complex expectations, while still maintaining type safety.
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+

|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
## Installation
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
```shell
|
|
95
|
+
npm i -D strong-mock
|
|
96
|
+
```
|
|
95
97
|
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
+
```shell
|
|
99
|
+
yarn add -D strong-mock
|
|
98
100
|
```
|
|
99
101
|
|
|
100
|
-
|
|
102
|
+
## Requirements
|
|
103
|
+
|
|
104
|
+
strong-mock requires an environment that supports the [ES6 Proxy object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). This is necessary to create dynamic mocks from types because TypeScript does not support reflection i.e. exposing the type info at runtime.
|
|
105
|
+
|
|
106
|
+
## API
|
|
107
|
+
|
|
108
|
+
### Setting expectations
|
|
109
|
+
|
|
110
|
+
Expectations are set by calling the mock inside a `when` callback and finishing it by setting a return value.
|
|
101
111
|
|
|
102
112
|
```typescript
|
|
103
|
-
|
|
113
|
+
when(() => foo.bar(23)).thenReturn('awesome');
|
|
104
114
|
```
|
|
105
115
|
|
|
106
116
|
### Setting multiple expectations
|
|
107
117
|
|
|
108
|
-
You can set as many expectations as you want by calling `when
|
|
118
|
+
You can set as many expectations as you want by calling `when` multiple times. If you have multiple expectations with the same arguments they will be consumed in the order they were created.
|
|
109
119
|
|
|
110
120
|
```typescript
|
|
111
|
-
when(foo.bar(23)).thenReturn('awesome');
|
|
112
|
-
when(foo.bar(23)).thenReturn('even more awesome');
|
|
121
|
+
when(() => foo.bar(23)).thenReturn('awesome');
|
|
122
|
+
when(() => foo.bar(23)).thenReturn('even more awesome');
|
|
113
123
|
|
|
114
|
-
console.log(
|
|
115
|
-
console.log(
|
|
124
|
+
console.log(foo.bar(23)); // awesome
|
|
125
|
+
console.log(foo.bar(23)); // even more awesome
|
|
116
126
|
```
|
|
117
127
|
|
|
118
128
|
By default, each call is expected to be called only once. You can expect a call to be made multiple times using the [invocation count](#setting-invocation-count-expectations) helpers.
|
|
@@ -124,12 +134,12 @@ You can expect a call to be made multiple times by using the invocation count he
|
|
|
124
134
|
```typescript
|
|
125
135
|
const fn = mock<(x: number) => number>();
|
|
126
136
|
|
|
127
|
-
when(fn(1)).thenReturn(1).between(2, 3);
|
|
137
|
+
when(() => fn(1)).thenReturn(1).between(2, 3);
|
|
128
138
|
|
|
129
|
-
console.log(
|
|
130
|
-
console.log(
|
|
131
|
-
console.log(
|
|
132
|
-
console.log(
|
|
139
|
+
console.log(fn(1)); // 1
|
|
140
|
+
console.log(fn(1)); // 1
|
|
141
|
+
console.log(fn(1)); // 1
|
|
142
|
+
console.log(fn(1)); // throws because the expectation is finished
|
|
133
143
|
```
|
|
134
144
|
|
|
135
145
|
### Mocking interfaces
|
|
@@ -144,11 +154,11 @@ interface Foo {
|
|
|
144
154
|
|
|
145
155
|
const foo = mock<Foo>();
|
|
146
156
|
|
|
147
|
-
when(foo.bar(23)).thenReturn('awesome');
|
|
148
|
-
when(foo.baz).thenReturn(100);
|
|
157
|
+
when(() => foo.bar(23)).thenReturn('awesome');
|
|
158
|
+
when(() => foo.baz).thenReturn(100);
|
|
149
159
|
|
|
150
|
-
console.log(
|
|
151
|
-
console.log(
|
|
160
|
+
console.log(foo.bar(23)); // 'awesome'
|
|
161
|
+
console.log(foo.baz); // 100
|
|
152
162
|
```
|
|
153
163
|
|
|
154
164
|
Since the mock is type safe the compiler will guarantee that you're only mocking things that actually exist on the interface.
|
|
@@ -162,9 +172,9 @@ type Fn = (x: number) => number;
|
|
|
162
172
|
|
|
163
173
|
const fn = mock<Fn>();
|
|
164
174
|
|
|
165
|
-
when(fn(1)).thenReturn(2);
|
|
175
|
+
when(() => fn(1)).thenReturn(2);
|
|
166
176
|
|
|
167
|
-
console.log(
|
|
177
|
+
console.log(fn(1)); // 2
|
|
168
178
|
```
|
|
169
179
|
|
|
170
180
|
### Mocking promises
|
|
@@ -176,9 +186,9 @@ type Fn = (x: number) => Promise<number>;
|
|
|
176
186
|
|
|
177
187
|
const fn = mock<Fn>();
|
|
178
188
|
|
|
179
|
-
when(fn(1)).thenResolve(2);
|
|
189
|
+
when(() => fn(1)).thenResolve(2);
|
|
180
190
|
|
|
181
|
-
console.log(await
|
|
191
|
+
console.log(await fn()); // 2
|
|
182
192
|
```
|
|
183
193
|
|
|
184
194
|
### Throwing errors
|
|
@@ -190,8 +200,8 @@ type FnWithPromise = (x: number) => Promise<void>;
|
|
|
190
200
|
const fn = mock<Fn>();
|
|
191
201
|
const fnWithPromise = mock<FnWithPromise>();
|
|
192
202
|
|
|
193
|
-
when(fn(1)).thenThrow();
|
|
194
|
-
when(fnWithPromise(1)).thenReject();
|
|
203
|
+
when(() => fn(1)).thenThrow();
|
|
204
|
+
when(() => fnWithPromise(1)).thenReject();
|
|
195
205
|
```
|
|
196
206
|
|
|
197
207
|
You'll notice there is no `never()` helper - if you expect a call to not be made simply don't set an expectation on it and the mock will throw if the call happens.
|
|
@@ -203,7 +213,7 @@ Calling `verify(mock)` will make sure that all expectations set on `mock` have b
|
|
|
203
213
|
```typescript
|
|
204
214
|
const fn = mock<(x: number) => number>();
|
|
205
215
|
|
|
206
|
-
when(fn(1)).thenReturn(1).between(2, 10);
|
|
216
|
+
when(() => fn(1)).thenReturn(1).between(2, 10);
|
|
207
217
|
|
|
208
218
|
verify(fn); // throws
|
|
209
219
|
```
|
|
@@ -214,7 +224,7 @@ It will also throw if any unexpected calls happened that were maybe caught in th
|
|
|
214
224
|
const fn = mock<() => void>();
|
|
215
225
|
|
|
216
226
|
try {
|
|
217
|
-
|
|
227
|
+
fn(); // throws because the call is unexpected
|
|
218
228
|
} catch(e) {
|
|
219
229
|
// your code might transition to an error state here
|
|
220
230
|
}
|
|
@@ -222,7 +232,7 @@ try {
|
|
|
222
232
|
verify(fn); // throws
|
|
223
233
|
```
|
|
224
234
|
|
|
225
|
-
It is recommended that
|
|
235
|
+
It is recommended that you call `verify()` on your mocks at the end of every test. This will make sure you don't have any unused expectations in your tests and that your code did not silently catch any of the errors that are thrown when an unexpected call happens. You can use `verifyAll()` to check all existing mocks e.g. in an `afterEach` hook.
|
|
226
236
|
|
|
227
237
|

|
|
228
238
|
|
|
@@ -233,11 +243,11 @@ You can remove all expectations from a mock by using the `reset()` method:
|
|
|
233
243
|
```typescript
|
|
234
244
|
const fn = mock<(x: number) => number>();
|
|
235
245
|
|
|
236
|
-
when(fn(1)).thenReturn(1);
|
|
246
|
+
when(() => fn(1)).thenReturn(1);
|
|
237
247
|
|
|
238
248
|
reset(fn);
|
|
239
249
|
|
|
240
|
-
|
|
250
|
+
fn(1); // throws
|
|
241
251
|
```
|
|
242
252
|
|
|
243
253
|
If you create common mocks that are shared by multiple tests you should reset them before using them e.g. in a `beforeEach` hook. You can use `resetAll()` to reset all existing mocks.
|
|
@@ -251,18 +261,26 @@ const fn = mock<
|
|
|
251
261
|
(x: number, data: { values: number[]; labels: string[] }) => string
|
|
252
262
|
>();
|
|
253
263
|
|
|
254
|
-
when(fn(
|
|
264
|
+
when(() => fn(
|
|
255
265
|
It.isAny(),
|
|
256
266
|
It.isObject({ values: [1, 2, 3] })
|
|
257
267
|
)).thenReturn('matched!');
|
|
258
268
|
|
|
259
|
-
console.log(
|
|
269
|
+
console.log(fn(
|
|
260
270
|
123,
|
|
261
271
|
{ values: [1, 2, 3], labels: ['a', 'b', 'c'] })
|
|
262
272
|
); // 'matched!'
|
|
263
273
|
```
|
|
264
274
|
|
|
275
|
+
You can mix argument matchers with concrete arguments:
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
when(() => fn(42, It.isObject())).thenReturn('matched');
|
|
279
|
+
```
|
|
280
|
+
|
|
265
281
|
Available matchers:
|
|
282
|
+
- `deepEquals` - the default, uses deep equality,
|
|
283
|
+
- `is` - uses `Object.is` for comparison,
|
|
266
284
|
- `isAny` - matches anything,
|
|
267
285
|
- `isNumber` - matches any number,
|
|
268
286
|
- `isString` - matches any string, can search for substrings and patterns,
|
|
@@ -271,6 +289,15 @@ Available matchers:
|
|
|
271
289
|
- `matches` - build your own matcher,
|
|
272
290
|
- `willCapture` - matches anything and stores the received value.
|
|
273
291
|
|
|
292
|
+
The following table illustrates the differences between the equality matchers:
|
|
293
|
+
|
|
294
|
+
| expected | actual | `It.is` | `It.deepEquals` | `It.deepEquals({ strict: false })` |
|
|
295
|
+
|--------------------|----------------------|-----------|-----------------|------------------------------------|
|
|
296
|
+
| `"foo"` | `"bar"` | equal | equal | equal |
|
|
297
|
+
| `{ foo: "bar" }` | `{ foo: "bar" }` | not equal | equal | equal |
|
|
298
|
+
| `{ }` | `{ foo: undefined }` | not equal | not equal | equal |
|
|
299
|
+
| `new (class {})()` | `new (class {})()` | not equal | not equal | equal |
|
|
300
|
+
|
|
274
301
|
Some matchers, like `isObject` and `isArray` support nesting matchers:
|
|
275
302
|
|
|
276
303
|
```typescript
|
|
@@ -286,9 +313,9 @@ You can create arbitrarily complex and type safe matchers with `It.matches(cb)`:
|
|
|
286
313
|
```typescript
|
|
287
314
|
const fn = mock<(x: number, y: number[]) => string>();
|
|
288
315
|
|
|
289
|
-
when(fn(
|
|
316
|
+
when(() => fn(
|
|
290
317
|
It.matches(x => x > 0),
|
|
291
|
-
It.matches(y => y.
|
|
318
|
+
It.matches(y => y.includes(42))
|
|
292
319
|
)).thenReturn('matched');
|
|
293
320
|
```
|
|
294
321
|
|
|
@@ -300,25 +327,86 @@ type Cb = (value: number) => number;
|
|
|
300
327
|
const fn = mock<(cb: Cb) => number>();
|
|
301
328
|
|
|
302
329
|
const matcher = It.willCapture<Cb>();
|
|
303
|
-
when(fn(matcher)).thenReturn(42);
|
|
330
|
+
when(() => fn(matcher)).thenReturn(42);
|
|
304
331
|
|
|
305
|
-
console.log(
|
|
332
|
+
console.log(fn(23, (x) => x + 1)); // 42
|
|
306
333
|
console.log(matcher.value?.(3)); // 4
|
|
307
334
|
```
|
|
308
335
|
|
|
336
|
+
### Mock options
|
|
337
|
+
|
|
338
|
+
#### Strictness
|
|
339
|
+
|
|
340
|
+
strong-mock has a few levels of "strictness" that control what values are returned when an unexpected property is accessed or an unexpected call is made. The strictness can be configured for each mock, or for all mocks with `setDefaults`.
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
import { mock, when } from 'strong-mock';
|
|
344
|
+
import { Strictness } from './options';
|
|
345
|
+
|
|
346
|
+
type Foo = {
|
|
347
|
+
bar: (value: number) => number;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// This is the default.
|
|
351
|
+
const strictFoo = mock<Foo>({ strictness: Strictness.STRICT });
|
|
352
|
+
|
|
353
|
+
// Accessing properties with no expectations is fine.
|
|
354
|
+
strictFoo.bar;
|
|
355
|
+
// Throws "Didn't expect bar(42) to be called".
|
|
356
|
+
strictFoo.bar(42);
|
|
357
|
+
|
|
358
|
+
const superStrictFoo = mock<Foo>({ strictness: Strictness.SUPER_STRICT });
|
|
359
|
+
|
|
360
|
+
// Throws "Didn't expect property bar to be accessed".
|
|
361
|
+
superStrictFoo.bar;
|
|
362
|
+
// Throws "Didn't expect property bar to be accessed".
|
|
363
|
+
superStrictFoo.bar(42);
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
#### Concrete matcher
|
|
367
|
+
|
|
368
|
+
You can set the matcher that will be used in expectations with concrete values e.g. `42` or `{ foo: "bar" }`. Passing in a [matcher argument](#argument-matchers) will always take priority.
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
import { mock, when, It } from 'strong-mock';
|
|
372
|
+
|
|
373
|
+
// Use strict equality instead of deep equality.
|
|
374
|
+
const fn = mock<(x: number[]) => boolean>({ concreteMatcher: It.is });
|
|
375
|
+
when(() => fn([1, 2, 3])).thenReturn(true);
|
|
376
|
+
|
|
377
|
+
fn([1, 2, 3]); // throws because different arrays
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
#### Defaults
|
|
381
|
+
|
|
382
|
+
Mock options can be set for all mocks with `setDefaults`.
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
import { mock, when, setDefaults, Strictness } from 'strong-mock';
|
|
386
|
+
|
|
387
|
+
setDefaults({
|
|
388
|
+
strictness: Strictness.SUPER_STRICT
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Uses the new default.
|
|
392
|
+
const superStrictMock = mock<() => void>();
|
|
393
|
+
// Overrides the default.
|
|
394
|
+
const strictMock = mock<() => void>({ strictness: Strictness.STRICT });
|
|
395
|
+
```
|
|
396
|
+
|
|
309
397
|
## FAQ
|
|
310
398
|
|
|
311
399
|
### Why do I have to set all expectations first?
|
|
312
400
|
|
|
313
401
|
This library is different from other mocking/spying libraries you might have used before such as [sinon](https://sinonjs.org) or [jest](https://jestjs.io/docs/en/mock-functions). Whereas those libraries are focused on recording calls to the mocks and always returning something, strong-mock requires you to set your expectations upfront. If a call happens that is not expected the mock will throw an error.
|
|
314
402
|
|
|
315
|
-
This design decision has a few reasons behind it. First
|
|
403
|
+
This design decision has a few reasons behind it. First, it forces you to be aware of what your code needs from its dependencies. Spying libraries encourage checking those needs at the end of the test after the code has already called the mocks. This can lead to tests missing dependency calls that just happen to not throw any error at runtime with the dummy values that the spies return.
|
|
316
404
|
|
|
317
405
|
Secondly, it will highlight potential design problems such as violations of the SOLID principles. If you find yourself duplicating expectations between tests and passing dummy values to them because your test is not concerned with them then you might want to look into splitting the code to only depend on things it really needs.
|
|
318
406
|
|
|
319
|
-
### Can I mock an existing object/function?
|
|
407
|
+
### Can I partially mock an existing object/function?
|
|
320
408
|
|
|
321
|
-
No,
|
|
409
|
+
No, passing a concrete implementation to `mock()` will be the same as passing a type: all properties will be mocked, and you have to set expectations on the ones that will be accessed.
|
|
322
410
|
|
|
323
411
|
### How do I set expectations on setters?
|
|
324
412
|
|
|
@@ -326,11 +414,11 @@ You currently can't do that. Please use a normal method instead e.g. `setFoo()`
|
|
|
326
414
|
|
|
327
415
|
### Why do I have to set a return value even if it's `undefined`?
|
|
328
416
|
|
|
329
|
-
To make side effects explicit and to prevent future refactoring headaches. If you
|
|
417
|
+
To make side effects explicit and to prevent future refactoring headaches. If you had just `when(() => fn())` and you later changed `fn()` to return a `number` then your expectation would become incorrect and the compiler couldn't check that for you.
|
|
330
418
|
|
|
331
419
|
### How do I provide a function for the mock to call?
|
|
332
420
|
|
|
333
|
-
There is no `thenCall()` method because it can't be safely typed - the type for `thenReturn()` is inferred from the return type in `when
|
|
421
|
+
There is no `thenCall()` method because it can't be safely typed - the type for `thenReturn()` is inferred from the return type in `when`, meaning that the required type would be the return value for the function, not the function itself. However, we can leverage this by setting an expectation on the function property instead:
|
|
334
422
|
|
|
335
423
|
```typescript
|
|
336
424
|
interface Foo {
|
|
@@ -339,73 +427,47 @@ interface Foo {
|
|
|
339
427
|
|
|
340
428
|
const foo = mock<Foo>();
|
|
341
429
|
|
|
342
|
-
when(foo.bar).thenReturn(x => `called ${x}`);
|
|
430
|
+
when(() => foo.bar).thenReturn(x => `called ${x}`);
|
|
343
431
|
|
|
344
|
-
console.log(
|
|
432
|
+
console.log(foo.bar(23)); // 'called 23'
|
|
345
433
|
```
|
|
346
434
|
|
|
347
|
-
The function in `thenReturn()` will be type checked against the actual interface so you can make sure you're passing in an implementation that makes sense. Moreover, refactoring the interface will also refactor the expectation (in a capable IDE).
|
|
348
|
-
|
|
349
|
-

|
|
435
|
+
The function in `thenReturn()` will be type checked against the actual interface, so you can make sure you're passing in an implementation that makes sense. Moreover, refactoring the interface will also refactor the expectation (in a capable IDE).
|
|
350
436
|
|
|
351
|
-
###
|
|
437
|
+
### Can I spread/enumerate a mock?
|
|
352
438
|
|
|
353
|
-
|
|
439
|
+
Yes, and you will only get the properties that have expectations on them.
|
|
354
440
|
|
|
355
441
|
```typescript
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
baz: () => number;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function doFoo(foo: Foo, { callBaz }: { callBaz: boolean }) {
|
|
362
|
-
// Will throw here with unexpected access on `baz`.
|
|
363
|
-
const { bar, baz } = foo;
|
|
364
|
-
|
|
365
|
-
bar();
|
|
442
|
+
const foo = mock<{ bar: number; baz: number }>();
|
|
443
|
+
when(() => foo.bar).thenReturn(42);
|
|
366
444
|
|
|
367
|
-
|
|
368
|
-
baz();
|
|
369
|
-
}
|
|
370
|
-
}
|
|
445
|
+
console.log(Object.keys(foo)); // ['bar']
|
|
371
446
|
|
|
372
|
-
const
|
|
373
|
-
when(foo.bar()).thenReturn(42);
|
|
447
|
+
const foo2 = { ...foo };
|
|
374
448
|
|
|
375
|
-
//
|
|
376
|
-
|
|
449
|
+
console.log(foo2.bar); // 42
|
|
450
|
+
console.log(foo2.baz); // undefined
|
|
377
451
|
```
|
|
378
452
|
|
|
379
|
-
|
|
453
|
+
### How can I ignore `undefined` keys when setting expectations on objects?
|
|
380
454
|
|
|
381
|
-
|
|
382
|
-
function doFoo(foo: Foo, callBaz: boolean) {
|
|
383
|
-
foo.bar();
|
|
455
|
+
Use the `It.deepEquals` matcher explicitly inside `when` and pass `{ strict: false }`:
|
|
384
456
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
```
|
|
457
|
+
```ts
|
|
458
|
+
const fn = mock<(x: { foo: string }) => boolean>();
|
|
390
459
|
|
|
391
|
-
|
|
460
|
+
when(() => fn(
|
|
461
|
+
It.deepEquals({ foo: "bar" }, { strict: false }))
|
|
462
|
+
).thenReturn(true);
|
|
392
463
|
|
|
393
|
-
|
|
394
|
-
when(foo.baz()).thenThrow('should not be called').anyTimes();
|
|
464
|
+
fn({ foo: "bar", baz: undefined }) === true
|
|
395
465
|
```
|
|
396
466
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
Yes, and you will only get the properties that have expectations on them.
|
|
400
|
-
|
|
401
|
-
```typescript
|
|
402
|
-
const foo = mock<{ bar: number; baz: number }>();
|
|
403
|
-
when(foo.bar).thenReturn(42);
|
|
404
|
-
|
|
405
|
-
console.log(Object.keys(instance(foo))); // ['bar']
|
|
406
|
-
|
|
407
|
-
const foo2 = { ...instance(foo) };
|
|
467
|
+
You can set this behavior to be the default by configuring the [concrete matcher](#concrete-matcher), and set it on all mocks using [setDefaults](#defaults):
|
|
408
468
|
|
|
409
|
-
|
|
410
|
-
|
|
469
|
+
```ts
|
|
470
|
+
setDefaults({
|
|
471
|
+
concreteMatcher: (expected) => It.deepEquals(expected, { strict: false })
|
|
472
|
+
});
|
|
411
473
|
```
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Expectation } from './expectation/expectation';
|
|
2
2
|
import { CallMap } from './expectation/repository/expectation-repository';
|
|
3
|
-
import { PendingExpectation } from './when/pending-expectation';
|
|
4
3
|
import { Property } from './proxy';
|
|
4
|
+
import { PendingExpectation } from './when/pending-expectation';
|
|
5
5
|
export declare class UnfinishedExpectation extends Error {
|
|
6
6
|
constructor(pendingExpectation: PendingExpectation);
|
|
7
7
|
}
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { Property } from '../proxy';
|
|
2
|
+
import { Matcher } from './matcher';
|
|
2
3
|
export declare type ReturnValue = {
|
|
3
4
|
value: any;
|
|
4
5
|
isPromise?: boolean;
|
|
5
6
|
isError?: boolean;
|
|
6
7
|
};
|
|
8
|
+
/**
|
|
9
|
+
* Compare received arguments against matchers.
|
|
10
|
+
*/
|
|
7
11
|
export interface Expectation {
|
|
8
12
|
property: Property;
|
|
9
13
|
/**
|
|
10
14
|
* `undefined` means this is a property expectation.
|
|
11
15
|
* `[]` means this is a function call with no arguments.
|
|
12
16
|
*/
|
|
13
|
-
args:
|
|
17
|
+
args: Matcher[] | undefined;
|
|
14
18
|
returnValue: ReturnValue;
|
|
15
19
|
min: number;
|
|
16
20
|
max: number;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Matcher, TypeMatcher } from './matcher';
|
|
2
|
+
declare type DeepPartial<T> = T extends object ? {
|
|
3
|
+
[K in keyof T]?: DeepPartial<T[K]>;
|
|
4
|
+
} : T;
|
|
5
|
+
/**
|
|
6
|
+
* Contains argument matchers that can be used to ignore arguments in an
|
|
7
|
+
* expectation or to match complex arguments.
|
|
8
|
+
*/
|
|
9
|
+
export declare const It: {
|
|
10
|
+
matches: <T>(cb: (actual: T) => boolean, { toJSON }?: {
|
|
11
|
+
toJSON?: (() => string) | undefined;
|
|
12
|
+
}) => TypeMatcher<T>;
|
|
13
|
+
deepEquals: <T_1>(expected: T_1, { strict }?: {
|
|
14
|
+
strict?: boolean | undefined;
|
|
15
|
+
}) => TypeMatcher<T_1>;
|
|
16
|
+
is: <T_2 = unknown>(expected: T_2) => TypeMatcher<T_2>;
|
|
17
|
+
isAny: () => TypeMatcher<any>;
|
|
18
|
+
isObject: <T_3 extends object, K extends DeepPartial<T_3>>(partial?: K | undefined) => TypeMatcher<T_3>;
|
|
19
|
+
isNumber: () => TypeMatcher<number>;
|
|
20
|
+
isString: ({ matching, containing, }?: {
|
|
21
|
+
matching?: RegExp | undefined;
|
|
22
|
+
containing?: string | undefined;
|
|
23
|
+
}) => TypeMatcher<string>;
|
|
24
|
+
isArray: <T_4 extends any[]>(containing?: T_4 | undefined) => TypeMatcher<T_4>;
|
|
25
|
+
willCapture: <T_5 = unknown>(name?: string) => T_5 & Matcher & {
|
|
26
|
+
value: T_5 | undefined;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
export {};
|
|
@@ -1,57 +1,21 @@
|
|
|
1
|
-
export declare
|
|
1
|
+
export declare const MATCHER_SYMBOL: unique symbol;
|
|
2
|
+
export declare type Matcher = {
|
|
2
3
|
/**
|
|
3
4
|
* Will be called with a value to match against.
|
|
4
5
|
*/
|
|
5
|
-
matches: (arg:
|
|
6
|
-
|
|
7
|
-
* TODO: turn into a symbol
|
|
8
|
-
*/
|
|
9
|
-
__isMatcher: boolean;
|
|
6
|
+
matches: (arg: any) => boolean;
|
|
7
|
+
[MATCHER_SYMBOL]: boolean;
|
|
10
8
|
/**
|
|
11
9
|
* Used by `pretty-format`.
|
|
12
10
|
*/
|
|
13
11
|
toJSON(): string;
|
|
14
12
|
};
|
|
15
13
|
/**
|
|
16
|
-
*
|
|
14
|
+
* This takes the shape of T to satisfy call sites, but strong-mock will only
|
|
15
|
+
* care about the matcher type.
|
|
17
16
|
*/
|
|
18
|
-
export declare
|
|
17
|
+
export declare type TypeMatcher<T> = T & Matcher;
|
|
19
18
|
/**
|
|
20
|
-
*
|
|
19
|
+
* Used to test if an expectation on an argument is a custom matcher.
|
|
21
20
|
*/
|
|
22
|
-
export declare
|
|
23
|
-
declare type DeepPartial<T> = T extends object ? {
|
|
24
|
-
[K in keyof T]?: DeepPartial<T[K]>;
|
|
25
|
-
} : T;
|
|
26
|
-
/**
|
|
27
|
-
* Contains argument matchers that can be used to ignore arguments in an
|
|
28
|
-
* expectation or to match complex arguments.
|
|
29
|
-
*/
|
|
30
|
-
export declare const It: {
|
|
31
|
-
isAny: () => Matcher<any>;
|
|
32
|
-
matches: <T>(cb: (arg: T) => boolean) => Matcher<T>;
|
|
33
|
-
isObject: <T_1 extends object, K extends DeepPartial<T_1>>(partial?: K | undefined) => Matcher<T_1>;
|
|
34
|
-
isNumber: () => Matcher<number>;
|
|
35
|
-
isString: ({ matching, containing, }?: {
|
|
36
|
-
matching?: RegExp | undefined;
|
|
37
|
-
containing?: string | undefined;
|
|
38
|
-
}) => Matcher<string>;
|
|
39
|
-
isArray: <T_2 extends any[]>(containing?: T_2 | undefined) => Matcher<T_2>;
|
|
40
|
-
willCapture: <T_3 = unknown>(name?: string | undefined) => T_3 & {
|
|
41
|
-
/**
|
|
42
|
-
* Will be called with a value to match against.
|
|
43
|
-
*/
|
|
44
|
-
matches: (arg: unknown) => boolean;
|
|
45
|
-
/**
|
|
46
|
-
* TODO: turn into a symbol
|
|
47
|
-
*/
|
|
48
|
-
__isMatcher: boolean;
|
|
49
|
-
/**
|
|
50
|
-
* Used by `pretty-format`.
|
|
51
|
-
*/
|
|
52
|
-
toJSON(): string;
|
|
53
|
-
} & {
|
|
54
|
-
value: T_3 | undefined;
|
|
55
|
-
};
|
|
56
|
-
};
|
|
57
|
-
export {};
|
|
21
|
+
export declare function isMatcher(f: unknown): f is Matcher;
|