strong-mock 8.0.0-beta.1 → 8.0.0

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 CHANGED
@@ -44,10 +44,10 @@ console.log(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
+ - [Mock options](#mock-options)
48
+ - [Unexpected property return value](#unexpected-property-return-value)
49
+ - [Exact params](#exact-params)
50
+ - [Concrete matcher](#concrete-matcher)
51
51
  - [FAQ](#faq)
52
52
  - [Why do I have to set all expectations first?](#why-do-i-have-to-set-all-expectations-first)
53
53
  - [Can I partially mock an existing object/function?](#can-i-partially-mock-an-existing-objectfunction)
@@ -63,7 +63,7 @@ console.log(foo.bar(23)); // 'I am strong!'
63
63
 
64
64
  ### Type safety
65
65
 
66
- The created mock matches the mocked type so all expectations are type safe. Moreover, refactorings in an IDE will also cover your expectations.
66
+ The mock expectations will share the same type guarantees as your production code, and you can safely refactor in an IDE knowing that all usages will be updated.
67
67
 
68
68
  ![Renaming production code and test code](media/rename-refactor.gif)
69
69
 
@@ -85,7 +85,7 @@ fn(4, 5, 6);
85
85
 
86
86
  ### Type safe argument matchers
87
87
 
88
- Optional argument matchers allow you to create complex expectations, while still maintaining type safety.
88
+ You can use argument matchers to partially match values, or create complex expectations, while still maintaining type safety.
89
89
 
90
90
  ![Type safe matcher showing a type error](media/type-safe-matchers.png)
91
91
 
@@ -125,11 +125,9 @@ console.log(foo.bar(23)); // awesome
125
125
  console.log(foo.bar(23)); // even more awesome
126
126
  ```
127
127
 
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.
129
-
130
128
  ### Setting invocation count expectations
131
129
 
132
- You can expect a call to be made multiple times by using the invocation count helpers `between`, `atLeast`, `times`, `anyTimes` etc.:
130
+ By default, each call is expected to be made only once. You can expect a call to be made multiple times by using the invocation count helpers `between`, `atLeast`, `times`, `anyTimes` etc.:
133
131
 
134
132
  ```typescript
135
133
  const fn = mock<(x: number) => number>();
@@ -142,6 +140,8 @@ console.log(fn(1)); // 1
142
140
  console.log(fn(1)); // throws because the expectation is finished
143
141
  ```
144
142
 
143
+ 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.
144
+
145
145
  ### Mocking interfaces
146
146
 
147
147
  Pass in the interface to the generic argument of `mock`:
@@ -161,8 +161,6 @@ console.log(foo.bar(23)); // 'awesome'
161
161
  console.log(foo.baz); // 100
162
162
  ```
163
163
 
164
- Since the mock is type safe the compiler will guarantee that you're only mocking things that actually exist on the interface.
165
-
166
164
  ### Mocking functions
167
165
 
168
166
  You can also mock functions similarly to interfaces:
@@ -179,7 +177,7 @@ console.log(fn(1)); // 2
179
177
 
180
178
  ### Mocking promises
181
179
 
182
- If you're mocking something that returns a promise then you'll be able to use the promise helpers to set the return value.
180
+ If you're mocking something that returns a promise then you'll be able to use the `thenResolve` promise helper to set the return value.
183
181
 
184
182
  ```typescript
185
183
  type Fn = (x: number) => Promise<number>;
@@ -193,29 +191,33 @@ console.log(await fn()); // 2
193
191
 
194
192
  ### Throwing errors
195
193
 
194
+ Use `thenThrow` or `thenReject` to throw an `Error` instance. You can customize the error message, or even pass a derived class.
195
+
196
196
  ```typescript
197
197
  type Fn = (x: number) => void;
198
198
  type FnWithPromise = (x: number) => Promise<void>;
199
199
 
200
+ class MyError extends Error {}
201
+
200
202
  const fn = mock<Fn>();
201
203
  const fnWithPromise = mock<FnWithPromise>();
202
204
 
205
+ // All of these will throw an Error instance.
203
206
  when(() => fn(1)).thenThrow();
204
- when(() => fnWithPromise(1)).thenReject();
207
+ when(() => fn(2)).thenThrow(MyError);
208
+ when(() => fnWithPromise(1)).thenReject('oops');
205
209
  ```
206
210
 
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.
208
-
209
211
  ### Verifying expectations
210
212
 
211
- Calling `verify(mock)` will make sure that all expectations set on `mock` have been met. If not, the function will throw an error and print the unmet expectations.
213
+ Calling `verify(myMock)` will make sure that all expectations set on the mock have been met, and that no additional calls have been made.
212
214
 
213
215
  ```typescript
214
216
  const fn = mock<(x: number) => number>();
215
217
 
216
218
  when(() => fn(1)).thenReturn(1).between(2, 10);
217
219
 
218
- verify(fn); // throws
220
+ verify(fn); // throws UnmetExpectations
219
221
  ```
220
222
 
221
223
  It will also throw if any unexpected calls happened that were maybe caught in the code under test.
@@ -229,10 +231,16 @@ try {
229
231
  // your code might transition to an error state here
230
232
  }
231
233
 
232
- verify(fn); // throws
234
+ verify(fn); // throws UnexpectedCalls
233
235
  ```
234
236
 
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.
237
+ 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.
238
+
239
+ ```typescript
240
+ afterEach(() => {
241
+ verifyAll();
242
+ })
243
+ ```
236
244
 
237
245
  ![verify error](./media/verify.png)
238
246
 
@@ -250,11 +258,17 @@ reset(fn);
250
258
  fn(1); // throws
251
259
  ```
252
260
 
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.
261
+ If you create common mocks that are shared by multiple tests you should reset them before each test. You can use `resetAll()` to reset all existing mocks.
262
+
263
+ ```typescript
264
+ beforeEach(() => {
265
+ resetAll();
266
+ })
267
+ ```
254
268
 
255
269
  ### Argument matchers
256
270
 
257
- Sometimes you're not interested in specifying all the arguments in an expectation. Maybe they've been covered in another test, maybe they're hard to specify e.g. callbacks. In those cases you can use argument matchers to either ignore some arguments or use custom matchers to check them.
271
+ Sometimes you're not interested in specifying all the arguments in an expectation. Maybe they've been covered in another test, maybe they're hard to specify e.g. callbacks, or maybe you want to match just a property from an argument.
258
272
 
259
273
  ```typescript
260
274
  const fn = mock<
@@ -293,7 +307,7 @@ The following table illustrates the differences between the equality matchers:
293
307
 
294
308
  | expected | actual | `It.is` | `It.deepEquals` | `It.deepEquals({ strict: false })` |
295
309
  |--------------------|----------------------|-----------|-----------------|------------------------------------|
296
- | `"foo"` | `"bar"` | equal | equal | equal |
310
+ | `"foo"` | `"foo"` | equal | equal | equal |
297
311
  | `{ foo: "bar" }` | `{ foo: "bar" }` | not equal | equal | equal |
298
312
  | `{ }` | `{ foo: undefined }` | not equal | not equal | equal |
299
313
  | `new (class {})()` | `new (class {})()` | not equal | not equal | equal |
@@ -333,65 +347,105 @@ console.log(fn(23, (x) => x + 1)); // 42
333
347
  console.log(matcher.value?.(3)); // 4
334
348
  ```
335
349
 
336
- ### Mock options
350
+ ## Mock options
351
+
352
+ The following options can be set per mock, or globally with `setDefaults`.
353
+
354
+ ```typescript
355
+ import { mock, when, setDefaults } from 'strong-mock';
356
+
357
+ setDefaults({
358
+ exactParams: true
359
+ });
360
+
361
+ // Uses the new default.
362
+ const superStrictMock = mock<() => void>();
363
+ // Overrides the default.
364
+ const strictMock = mock<() => void>({ exactParams: false });
365
+ ```
337
366
 
338
- #### Strictness
367
+ ### Unexpected property return value
339
368
 
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`.
369
+ You can control what happens whenever an unexpected property is accessed, or an unexpected call is made.
341
370
 
342
371
  ```typescript
343
- import { mock, when } from 'strong-mock';
344
- import { Strictness } from './options';
372
+ import { mock, when, UnexpectedProperty } from 'strong-mock';
345
373
 
346
374
  type Foo = {
347
375
  bar: (value: number) => number;
348
376
  }
349
377
 
350
378
  // This is the default.
351
- const strictFoo = mock<Foo>({ strictness: Strictness.STRICT });
379
+ const callsThrow = mock<Foo>({
380
+ unexpectedProperty: UnexpectedProperty.CALL_THROW
381
+ });
352
382
 
353
383
  // Accessing properties with no expectations is fine.
354
- strictFoo.bar;
384
+ callsThrow.bar;
355
385
  // Throws "Didn't expect bar(42) to be called".
356
- strictFoo.bar(42);
386
+ callsThrow.bar(42);
357
387
 
358
- const superStrictFoo = mock<Foo>({ strictness: Strictness.SUPER_STRICT });
388
+ const propertiesThrow = mock<Foo>({
389
+ unexpectedProperty: UnexpectedProperty.THROW
390
+ });
359
391
 
360
392
  // Throws "Didn't expect property bar to be accessed".
361
- superStrictFoo.bar;
393
+ propertiesThrow.bar;
362
394
  // Throws "Didn't expect property bar to be accessed".
363
- superStrictFoo.bar(42);
395
+ propertiesThrow.bar(42);
364
396
  ```
365
397
 
366
- #### Concrete matcher
398
+ ### Exact params
367
399
 
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.
400
+ By default, function/method expectations will allow more arguments to be received than expected. Since the expectations are type safe, the TypeScript compiler will never allow expecting less arguments than required. Unspecified optional arguments will be considered ignored, as if they've been replaced with [argument matchers](#argument-matchers).
369
401
 
370
402
  ```typescript
371
- import { mock, when, It } from 'strong-mock';
403
+ import { mock } from 'strong-mock';
372
404
 
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);
405
+ const fn = mock<(value?: number) => number>();
406
+
407
+ when(() => fn()).thenReturn(42).twice();
376
408
 
377
- fn([1, 2, 3]); // throws because different arrays
409
+ // Since the expectation doesn't expect any arguments,
410
+ // both of the following are fine
411
+ console.log(fn()); // 42
412
+ console.log(fn(1)); // 42
378
413
  ```
379
414
 
380
- #### Defaults
415
+ If you're not using TypeScript, or you want to be super strict, you can set `exactParams: true`.
416
+
417
+ ```typescript
418
+ import { mock } from 'strong-mock';
419
+
420
+ const fn = mock<(optionalValue?: number) => number>({
421
+ exactParams: true
422
+ });
381
423
 
382
- Mock options can be set for all mocks with `setDefaults`.
424
+ when(() => fn()).thenReturn(42).twice();
425
+
426
+ console.log(fn()); // 42
427
+ console.log(fn(1)); // throws
428
+ ```
429
+
430
+ ### Concrete matcher
431
+
432
+ You can configure the [matcher](#argument-matchers) that will be used in expectations with concrete values e.g. `42` or `{ foo: "bar" }`. This matcher can always be overwritten inside an expectation with another matcher.
383
433
 
384
434
  ```typescript
385
- import { mock, when, setDefaults, Strictness } from 'strong-mock';
435
+ import { mock, when, It } from 'strong-mock';
386
436
 
387
- setDefaults({
388
- strictness: Strictness.SUPER_STRICT
437
+ // Use strict equality instead of deep equality.
438
+ const fn = mock<(x: number[], y: string) => boolean>({
439
+ concreteMatcher: It.is
389
440
  });
441
+ when(() => fn([1, 2, 3], 'foo')).thenReturn(true);
390
442
 
391
- // Uses the new default.
392
- const superStrictMock = mock<() => void>();
393
- // Overrides the default.
394
- const strictMock = mock<() => void>({ strictness: Strictness.STRICT });
443
+ fn([1, 2, 3], 'foo'); // throws because different array instances
444
+
445
+ const arr = [1, 2, 3];
446
+ // The matcher will only apply to non-matcher arguments.
447
+ when(() => fn(arr, It.isString())).thenReturn(true);
448
+ console.log(fn(arr, 'any string')); // true
395
449
  ```
396
450
 
397
451
  ## FAQ
@@ -402,7 +456,7 @@ This library is different from other mocking/spying libraries you might have use
402
456
 
403
457
  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.
404
458
 
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.
459
+ 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.
406
460
 
407
461
  ### Can I partially mock an existing object/function?
408
462
 
@@ -414,7 +468,7 @@ You currently can't do that. Please use a normal method instead e.g. `setFoo()`
414
468
 
415
469
  ### Why do I have to set a return value even if it's `undefined`?
416
470
 
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.
471
+ 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.
418
472
 
419
473
  ### How do I provide a function for the mock to call?
420
474
 
@@ -464,7 +518,7 @@ when(() => fn(
464
518
  fn({ foo: "bar", baz: undefined }) === true
465
519
  ```
466
520
 
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):
521
+ You can set this behavior to be the default by configuring the [concrete matcher](#concrete-matcher).
468
522
 
469
523
  ```ts
470
524
  setDefaults({
package/dist/errors.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { Expectation } from './expectation/expectation';
2
- import { CallMap } from './expectation/repository/expectation-repository';
3
- import { Property } from './proxy';
4
- import { PendingExpectation } from './when/pending-expectation';
1
+ import type { Expectation } from './expectation/expectation';
2
+ import type { CallMap } from './expectation/repository/expectation-repository';
3
+ import type { Property } from './proxy';
4
+ import type { PendingExpectation } from './when/pending-expectation';
5
5
  export declare class UnfinishedExpectation extends Error {
6
6
  constructor(pendingExpectation: PendingExpectation);
7
7
  }
@@ -1,10 +1,6 @@
1
- import { Property } from '../proxy';
2
- import { Matcher } from './matcher';
3
- export declare type ReturnValue = {
4
- value: any;
5
- isPromise?: boolean;
6
- isError?: boolean;
7
- };
1
+ import type { Property } from '../proxy';
2
+ import type { Matcher } from './matcher';
3
+ import type { ReturnValue } from './repository/return-value';
8
4
  /**
9
5
  * Compare received arguments against matchers.
10
6
  */
@@ -1,4 +1,4 @@
1
- import { Matcher, TypeMatcher } from './matcher';
1
+ import type { Matcher, TypeMatcher } from './matcher';
2
2
  declare type DeepPartial<T> = T extends object ? {
3
3
  [K in keyof T]?: DeepPartial<T[K]>;
4
4
  } : T;
@@ -1,5 +1,5 @@
1
- import { Property } from '../../proxy';
2
- import { Expectation, ReturnValue } from '../expectation';
1
+ import type { Property } from '../../proxy';
2
+ import type { Expectation } from '../expectation';
3
3
  export declare type Call = {
4
4
  arguments: any[] | undefined;
5
5
  };
@@ -41,17 +41,17 @@ export interface ExpectationRepository {
41
41
  *
42
42
  * @example
43
43
  * add(new Expectation('getData', [1, 2], 23);
44
- * get('getData').value(1, 2) === 23
44
+ * get('getData')(1, 2) === 23
45
45
  *
46
46
  * @example
47
47
  * add(new Expectation('hasData', undefined, true);
48
- * get('hasData').value === true
48
+ * get('hasData') === true
49
49
  *
50
50
  * @example
51
51
  * add(new Expectation('getData', undefined, () => 42);
52
- * get('getData').value(1, 2, '3', false, NaN) === 42
52
+ * get('getData')(1, 2, '3', false, NaN) === 42
53
53
  */
54
- get(property: Property): ReturnValue;
54
+ get(property: Property): unknown;
55
55
  /**
56
56
  * Get a return value for a function call.
57
57
  *
@@ -1,17 +1,18 @@
1
- import { Strictness } from '../../mock/options';
2
- import { Property } from '../../proxy';
3
- import { Expectation } from '../expectation';
4
- import { CallMap, ExpectationRepository } from './expectation-repository';
1
+ import { UnexpectedProperty } from '../../mock/options';
2
+ import type { Property } from '../../proxy';
3
+ import type { Expectation } from '../expectation';
4
+ import type { CallMap, ExpectationRepository } from './expectation-repository';
5
5
  declare type CountableExpectation = {
6
6
  expectation: Expectation;
7
7
  matchCount: number;
8
8
  };
9
9
  /**
10
- * An expectation repository for configurable levels of strictness.
10
+ * An expectation repository with a configurable behavior for
11
+ * unexpected property access.
11
12
  */
12
13
  export declare class FlexibleRepository implements ExpectationRepository {
13
- private strictness;
14
- constructor(strictness?: Strictness);
14
+ private unexpectedProperty;
15
+ constructor(unexpectedProperty?: UnexpectedProperty);
15
16
  protected readonly expectations: Map<Property, CountableExpectation[]>;
16
17
  private readonly expectedCallStats;
17
18
  private readonly unexpectedCallStats;
@@ -0,0 +1,13 @@
1
+ export declare type ReturnValue = {
2
+ value: any;
3
+ isPromise?: boolean;
4
+ isError?: boolean;
5
+ };
6
+ /**
7
+ * Unbox the expectation's return value.
8
+ *
9
+ * If the value is an error then throw it.
10
+ *
11
+ * If the value is a promise then resolve/reject it.
12
+ */
13
+ export declare const unboxReturnValue: ({ isError, isPromise, value, }: ReturnValue) => any;
@@ -1,6 +1,7 @@
1
- import { Property } from '../proxy';
2
- import { Expectation, ReturnValue } from './expectation';
3
- import { Matcher } from './matcher';
1
+ import type { Property } from '../proxy';
2
+ import type { Expectation } from './expectation';
3
+ import type { Matcher } from './matcher';
4
+ import type { ReturnValue } from './repository/return-value';
4
5
  /**
5
6
  * Matches a call with more parameters than expected because it is assumed the
6
7
  * compiler will check that those parameters are optional.
@@ -16,10 +17,11 @@ export declare class StrongExpectation implements Expectation {
16
17
  property: Property;
17
18
  args: Matcher[] | undefined;
18
19
  returnValue: ReturnValue;
20
+ private exactParams;
19
21
  private matched;
20
22
  min: number;
21
23
  max: number;
22
- constructor(property: Property, args: Matcher[] | undefined, returnValue: ReturnValue);
24
+ constructor(property: Property, args: Matcher[] | undefined, returnValue: ReturnValue, exactParams?: boolean);
23
25
  setInvocationCount(min: number, max?: number): void;
24
26
  matches(args: any[] | undefined): boolean;
25
27
  isUnmet(): boolean;
package/dist/index.d.ts CHANGED
@@ -6,4 +6,4 @@ export { It } from './expectation/it';
6
6
  export { setDefaults } from './mock/defaults';
7
7
  export type { Matcher } from './expectation/matcher';
8
8
  export type { MockOptions } from './mock/options';
9
- export { Strictness } from './mock/options';
9
+ export { UnexpectedProperty } from './mock/options';