strong-mock 8.0.0-beta.1 → 8.0.0-beta.2

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 called 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:
@@ -204,18 +202,16 @@ when(() => fn(1)).thenThrow();
204
202
  when(() => fnWithPromise(1)).thenReject();
205
203
  ```
206
204
 
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
205
  ### Verifying expectations
210
206
 
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.
207
+ 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
208
 
213
209
  ```typescript
214
210
  const fn = mock<(x: number) => number>();
215
211
 
216
212
  when(() => fn(1)).thenReturn(1).between(2, 10);
217
213
 
218
- verify(fn); // throws
214
+ verify(fn); // throws UnmetExpectations
219
215
  ```
220
216
 
221
217
  It will also throw if any unexpected calls happened that were maybe caught in the code under test.
@@ -229,10 +225,16 @@ try {
229
225
  // your code might transition to an error state here
230
226
  }
231
227
 
232
- verify(fn); // throws
228
+ verify(fn); // throws UnexpectedCalls
233
229
  ```
234
230
 
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.
231
+ 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.
232
+
233
+ ```typescript
234
+ afterEach(() => {
235
+ verifyAll();
236
+ })
237
+ ```
236
238
 
237
239
  ![verify error](./media/verify.png)
238
240
 
@@ -250,11 +252,17 @@ reset(fn);
250
252
  fn(1); // throws
251
253
  ```
252
254
 
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.
255
+ 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.
256
+
257
+ ```typescript
258
+ beforeEach(() => {
259
+ resetAll();
260
+ })
261
+ ```
254
262
 
255
263
  ### Argument matchers
256
264
 
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.
265
+ 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
266
 
259
267
  ```typescript
260
268
  const fn = mock<
@@ -293,7 +301,7 @@ The following table illustrates the differences between the equality matchers:
293
301
 
294
302
  | expected | actual | `It.is` | `It.deepEquals` | `It.deepEquals({ strict: false })` |
295
303
  |--------------------|----------------------|-----------|-----------------|------------------------------------|
296
- | `"foo"` | `"bar"` | equal | equal | equal |
304
+ | `"foo"` | `"foo"` | equal | equal | equal |
297
305
  | `{ foo: "bar" }` | `{ foo: "bar" }` | not equal | equal | equal |
298
306
  | `{ }` | `{ foo: undefined }` | not equal | not equal | equal |
299
307
  | `new (class {})()` | `new (class {})()` | not equal | not equal | equal |
@@ -333,65 +341,100 @@ console.log(fn(23, (x) => x + 1)); // 42
333
341
  console.log(matcher.value?.(3)); // 4
334
342
  ```
335
343
 
336
- ### Mock options
344
+ ## Mock options
337
345
 
338
- #### Strictness
346
+ The following options can be set per mock, or globally with `setDefaults`.
339
347
 
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`.
348
+ ```typescript
349
+ import { mock, when, setDefaults } from 'strong-mock';
350
+
351
+ setDefaults({
352
+ exactParams: true
353
+ });
354
+
355
+ // Uses the new default.
356
+ const superStrictMock = mock<() => void>();
357
+ // Overrides the default.
358
+ const strictMock = mock<() => void>({ exactParams: false });
359
+ ```
360
+
361
+ ### Unexpected property return value
362
+
363
+ You can control what happens whenever an unexpected property is accessed, or an unexpected call is made.
341
364
 
342
365
  ```typescript
343
- import { mock, when } from 'strong-mock';
344
- import { Strictness } from './options';
366
+ import { mock, when, UnexpectedProperty } from 'strong-mock';
345
367
 
346
368
  type Foo = {
347
369
  bar: (value: number) => number;
348
370
  }
349
371
 
350
372
  // This is the default.
351
- const strictFoo = mock<Foo>({ strictness: Strictness.STRICT });
373
+ const callsThrow = mock<Foo>({
374
+ unexpectedProperty: UnexpectedProperty.CALL_THROW
375
+ });
352
376
 
353
377
  // Accessing properties with no expectations is fine.
354
- strictFoo.bar;
378
+ callsThrow.bar;
355
379
  // Throws "Didn't expect bar(42) to be called".
356
- strictFoo.bar(42);
380
+ callsThrow.bar(42);
357
381
 
358
- const superStrictFoo = mock<Foo>({ strictness: Strictness.SUPER_STRICT });
382
+ const propertiesThrow = mock<Foo>({
383
+ unexpectedProperty: UnexpectedProperty.THROW
384
+ });
359
385
 
360
386
  // Throws "Didn't expect property bar to be accessed".
361
- superStrictFoo.bar;
387
+ propertiesThrow.bar;
362
388
  // Throws "Didn't expect property bar to be accessed".
363
- superStrictFoo.bar(42);
389
+ propertiesThrow.bar(42);
364
390
  ```
365
391
 
366
- #### Concrete matcher
392
+ ### Exact params
367
393
 
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.
394
+ 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
395
 
370
396
  ```typescript
371
- import { mock, when, It } from 'strong-mock';
397
+ import { mock } from 'strong-mock';
372
398
 
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);
399
+ const fn = mock<(value?: number) => number>();
376
400
 
377
- fn([1, 2, 3]); // throws because different arrays
401
+ when(() => fn()).thenReturn(42).twice();
402
+
403
+ // Since the expectation doesn't expect any arguments,
404
+ // both of the following are fine
405
+ console.log(fn()); // 42
406
+ console.log(fn(1)); // 42
378
407
  ```
379
408
 
380
- #### Defaults
409
+ If you're not using TypeScript, or you want to be super strict, you can set `exactParams: true`.
410
+
411
+ ```typescript
412
+ import { mock } from 'strong-mock';
413
+
414
+ const fn = mock<(optionalValue?: number) => number>({
415
+ exactParams: true
416
+ });
381
417
 
382
- Mock options can be set for all mocks with `setDefaults`.
418
+ when(() => fn()).thenReturn(42).twice();
419
+
420
+ console.log(fn()); // 42
421
+ console.log(fn(1)); // throws
422
+ ```
423
+
424
+ ### Concrete matcher
425
+
426
+ 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.
383
427
 
384
428
  ```typescript
385
- import { mock, when, setDefaults, Strictness } from 'strong-mock';
429
+ import { mock, when, It } from 'strong-mock';
386
430
 
387
- setDefaults({
388
- strictness: Strictness.SUPER_STRICT
431
+ // Use strict equality instead of deep equality.
432
+ const fn = mock<(x: number[]) => boolean>({
433
+ concreteMatcher: It.is
389
434
  });
435
+ when(() => fn([1, 2, 3])).thenReturn(true);
390
436
 
391
- // Uses the new default.
392
- const superStrictMock = mock<() => void>();
393
- // Overrides the default.
394
- const strictMock = mock<() => void>({ strictness: Strictness.STRICT });
437
+ fn([1, 2, 3]); // throws because different array instances
395
438
  ```
396
439
 
397
440
  ## FAQ
@@ -402,7 +445,7 @@ This library is different from other mocking/spying libraries you might have use
402
445
 
403
446
  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
447
 
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.
448
+ 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
449
 
407
450
  ### Can I partially mock an existing object/function?
408
451
 
@@ -414,7 +457,7 @@ You currently can't do that. Please use a normal method instead e.g. `setFoo()`
414
457
 
415
458
  ### Why do I have to set a return value even if it's `undefined`?
416
459
 
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.
460
+ 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
461
 
419
462
  ### How do I provide a function for the mock to call?
420
463
 
@@ -464,7 +507,7 @@ when(() => fn(
464
507
  fn({ foo: "bar", baz: undefined }) === true
465
508
  ```
466
509
 
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):
510
+ You can set this behavior to be the default by configuring the [concrete matcher](#concrete-matcher).
468
511
 
469
512
  ```ts
470
513
  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';