You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Stub behaviors of [vitest][]mocks based on how they are called with a small, readable, and opinionated API. Inspired by [testdouble.js][] and [jest-when][].
7
+
Stub behaviors of [Vitest][]mock functions with a small, readable API. Inspired by [testdouble.js][] and [jest-when][].
[Vitest mock functions][]are powerful, but have an overly permissive API, inherited from Jest. This API makes it hard to use mocks to their full potential of providing meaningful design feedback while writing tests.
25
+
Create [stubs][]- fake objects that have pre-configured responses to matching arguments - from [Vitest's mock functions][]. With vitest-when, your stubs are:
26
26
27
-
-It's easy to make silly mistakes, like mocking a return value without checking the arguments.
28
-
-Mock usage requires calls in both the [arrange and assert][] phases a test (e.g. configure return value, assert called with proper arguments), which harms test readability and maintainability.
27
+
-Easy to read
28
+
-Hard to misconfigure, especially when using TypeScript
29
29
30
-
To avoid these issues, vitest-when wraps vitest mocks in a focused, opinionated API that allows you to configure mock behaviors if and only if they are called as you expect.
30
+
Wrap your `vi.fn()` mock - or a function imported from a `vi.mock`'d module - in [`when`][when], match on a set of arguments using [`calledWith`][called-with], and configure a behavior
[arrange and assert]: https://github.com/testdouble/contributing-tests/wiki/Arrange-Act-Assert
32
+
-[`.thenReturn()`][then-return] - Return a value
33
+
-[`.thenResolve()`][then-resolve] - Resolve a `Promise`
34
+
-[`.thenThrow()`][then-throw] - Throw an error
35
+
-[`.thenReject()`][then-reject] - Reject a `Promise`
36
+
-[`.thenDo()`][then-do] - Trigger a function
34
37
35
-
## Usage
38
+
If the stub is called with arguments that match `calledWith`, the configured behavior will occur. If the arguments do not match, the stub will no-op and return `undefined`.
39
+
40
+
```ts
41
+
import { vi, test, afterEach } from'vitest';
42
+
import { when } from'';
43
+
44
+
afterEach(() => {
45
+
vi.resetAllMocks();
46
+
});
47
+
48
+
test('stubbing with vitest-when', () => {
49
+
const stub =vi.fn();
50
+
51
+
when(stub).calledWith(1, 2, 3).thenReturn(4);
52
+
when(stub).calledWith(4, 5, 6).thenReturn(7);
53
+
54
+
const result123 =stub(1, 2, 3);
55
+
expect(result).toBe(4);
56
+
57
+
const result456 =stub(4, 5, 6);
58
+
expect(result).toBe(7);
59
+
60
+
const result789 =stub(7, 8, 9);
61
+
expect(result).toBe(undefined);
62
+
});
63
+
```
64
+
65
+
You should call `vi.resetAllMocks()` in your suite's `afterEach` hook to remove the implementation added by `when`. You can also set Vitest's [`mockReset`](https://vitest.dev/config/#mockreset) config to `true` instead of using `afterEach`.
Vitest's mock functions are powerful, but have an overly permissive API, inherited from Jest. Vanilla `vi.fn()` mock functions are difficult to use well and easy to use poorly.
80
+
81
+
- Mock usage is spread across the [arrange and assert][] phases of your test, with "act" in between, making the test harder to read.
82
+
- If you forget the `expect(...).toHaveBeenCalledWith(...)` step, the test will pass even if the mock is called incorrectly.
83
+
-`expect(...).toHaveBeenCalledWith(...)` is not type-checked, as of Vitest `0.31.0`.
84
+
85
+
```ts
86
+
// arrange
87
+
const stub =vi.fn();
88
+
stub.mockReturnValue('world');
89
+
90
+
// act
91
+
const result =stub('hello');
92
+
93
+
// assert
94
+
expect(stub).toHaveBeenCalledWith('hello');
95
+
expect(result).toBe('world');
96
+
```
97
+
98
+
In contrast, when using vitest-when stubs:
36
99
37
-
0. Add `vi.resetAllMocks` to your suite's `afterEach` hook
38
-
1. Use `when(mock).calledWith(...)` to specify matching arguments
39
-
2. Configure a behavior with a stub method:
40
-
- Return a value: `.thenReturn(...)`
41
-
- Resolve a `Promise`: `.thenResolve(...)`
42
-
- Throw an error: `.thenThrow(...)`
43
-
- Reject a `Promise`: `.thenReject(...)`
44
-
- Trigger a callback: `.thenDo(...)`
100
+
- All stub configuration happens in the "arrange" phase of your test.
101
+
- You cannot forget `calledWith`.
102
+
-`calledWith` and `thenReturn` (et. al.) are fully type-checked.
Create's a stub for a given set of arguments that you can then configure with different behaviors.
186
+
Configures a `vi.fn()` mock function to act as a vitest-when stub. Adds an implementation to the function that initially no-ops, and returns an API to configure behaviors for given arguments using [`.calledWith(...)`][called-with]
Create a stub that matches a given set of arguments which you can configure with different behaviors using methods like [`.thenReturn(...)`][then-return].
When a call to a mock uses arguments that match those given to `calledWith`, a configured behavior will be triggered. All arguments must match, though you can use vitest's [asymmetric matchers][] to loosen the stubbing:
209
+
When a call to a mock uses arguments that match those given to `calledWith`, a configured behavior will be triggered. All arguments must match, but you can use Vitest's [asymmetric matchers][] to loosen the stubbing:
119
210
120
211
```ts
121
212
const spy =vi.fn();
@@ -338,10 +429,3 @@ when(spy)
338
429
expect(spy('hello')).toEqual('world');
339
430
expect(spy('hello')).toEqual('solar system');
340
431
```
341
-
342
-
## See also
343
-
344
-
- [testdouble-vitest][] - Use [testdouble.js][] mocks with Vitest instead of the default [tinyspy][] mocks.
0 commit comments