Skip to content

Commit 067fc34

Browse files
authored
feat: return mock from then methods (#27)
1 parent 24a97c2 commit 067fc34

File tree

9 files changed

+319
-315
lines changed

9 files changed

+319
-315
lines changed

README.md

Lines changed: 82 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,13 @@ You should call `vi.resetAllMocks()` in your suite's `afterEach` hook to remove
6969

7070
[vitest's mock functions]: https://vitest.dev/api/mock.html
7171
[stubs]: https://en.wikipedia.org/wiki/Test_stub
72-
[when]: #whenspy-tfunc-stubwrappertfunc
73-
[called-with]: #calledwithargs-targs-stubtargs-treturn
74-
[then-return]: #thenreturnvalue-treturn
75-
[then-resolve]: #thenresolvevalue-treturn
76-
[then-throw]: #thenthrowerror-unknown
77-
[then-reject]: #thenrejecterror-unknown
78-
[then-do]: #thendocallback-args-targs--treturn
72+
[when]: #whenmock-tfunc-options-whenoptions-stubwrappertfunc
73+
[called-with]: #calledwithargs-parameterstfunc-stubtfunc
74+
[then-return]: #thenreturnvalue-treturn---mocktfunc
75+
[then-resolve]: #thenresolvevalue-treturn---mocktfunc
76+
[then-throw]: #thenthrowerror-unknown---mocktfunc
77+
[then-reject]: #thenrejecterror-unknown---mocktfunc
78+
[then-do]: #thendocallback-args-targs--treturn---mocktfunc
7979

8080
### Why not vanilla Vitest mocks?
8181

@@ -184,19 +184,19 @@ export const calculateQuestion = async (answer: number): Promise<string> => {
184184

185185
## API
186186

187-
### `when(spy: TFunc, options?: WhenOptions): StubWrapper<TFunc>`
187+
### `when(mock: TFunc, options?: WhenOptions): StubWrapper<TFunc>`
188188

189189
Configures a `vi.fn()` or `vi.spyOn()` 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]
190190

191191
```ts
192192
import { vi } from 'vitest'
193193
import { when } from 'vitest-when'
194194

195-
const spy = vi.fn()
195+
const mock = vi.fn()
196196

197-
when(spy)
197+
when(mock)
198198

199-
expect(spy()).toBe(undefined)
199+
expect(mock()).toBe(undefined)
200200
```
201201

202202
#### Options
@@ -209,36 +209,32 @@ import type { WhenOptions } from 'vitest-when'
209209
| ------- | ------- | ------- | -------------------------------------------------- |
210210
| `times` | N/A | integer | Only trigger configured behavior a number of times |
211211

212-
### `.calledWith(...args: TArgs): Stub<TArgs, TReturn>`
212+
### `.calledWith(...args: Parameters<TFunc>): Stub<TFunc>`
213213

214214
Create a stub that matches a given set of arguments which you can configure with different behaviors using methods like [`.thenReturn(...)`][then-return].
215215

216216
```ts
217-
const spy = vi.fn()
217+
const mock = when(vi.fn()).calledWith('hello').thenReturn('world')
218218

219-
when(spy).calledWith('hello').thenReturn('world')
220-
221-
expect(spy('hello')).toEqual('world')
219+
expect(mock('hello')).toEqual('world')
222220
```
223221

224222
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:
225223

226224
```ts
227-
const spy = vi.fn()
228-
229-
when(spy).calledWith(expect.any(String)).thenReturn('world')
225+
const mock = when(vi.fn()).calledWith(expect.any(String)).thenReturn('world')
230226

231-
expect(spy('hello')).toEqual('world')
232-
expect(spy('anything')).toEqual('world')
227+
expect(mock('hello')).toEqual('world')
228+
expect(mock('anything')).toEqual('world')
233229
```
234230

235231
If `calledWith` is used multiple times, the last configured stubbing will be used.
236232

237233
```ts
238-
when(spy).calledWith('hello').thenReturn('world')
239-
expect(spy('hello')).toEqual('world')
240-
when(spy).calledWith('hello').thenReturn('goodbye')
241-
expect(spy('hello')).toEqual('goodbye')
234+
when(mock).calledWith('hello').thenReturn('world')
235+
expect(mock('hello')).toEqual('world')
236+
when(mock).calledWith('hello').thenReturn('goodbye')
237+
expect(mock('hello')).toEqual('goodbye')
242238
```
243239

244240
[asymmetric matchers]: https://vitest.dev/api/expect.html#expect-anything
@@ -269,184 +265,165 @@ when<() => null>(overloaded).calledWith().thenReturn(null)
269265
By default, if arguments do not match, a vitest-when stub will no-op and return `undefined`. You can customize this fallback by configuring your own unconditional behavior on the mock using Vitest's built-in [mock API][].
270266

271267
```ts
272-
const spy = vi.fn().mockReturnValue('you messed up!')
273-
274-
when(spy).calledWith('hello').thenReturn('world')
268+
const mock = when(vi.fn(() => 'you messed up!')))
269+
.calledWith('hello')
270+
.thenReturn('world')
275271

276-
spy('hello') // "world"
277-
spy('jello') // "you messed up!"
272+
mock('hello') // "world"
273+
mock('jello') // "you messed up!"
278274
```
279275

280276
[mock API]: https://vitest.dev/api/mock.html
281277

282-
### `.thenReturn(value: TReturn)`
278+
### `.thenReturn(value: TReturn) -> Mock<TFunc>`
283279

284280
When the stubbing is satisfied, return `value`
285281

286282
```ts
287-
const spy = vi.fn()
288-
289-
when(spy).calledWith('hello').thenReturn('world')
283+
const mock = when(vi.fn()).calledWith('hello').thenReturn('world')
290284

291-
expect(spy('hello')).toEqual('world')
285+
expect(mock('hello')).toEqual('world')
292286
```
293287

294288
To only return a value once, use the `times` option.
295289

296290
```ts
297291
import { when } from 'vitest-when'
298292

299-
const spy = vi.fn()
300-
301-
when(spy, { times: 1 }).calledWith('hello').thenReturn('world')
293+
const mock = when(vi.fn(), { times: 1 }).calledWith('hello').thenReturn('world')
302294

303-
expect(spy('hello')).toEqual('world')
304-
expect(spy('hello')).toEqual(undefined)
295+
expect(mock('hello')).toEqual('world')
296+
expect(mock('hello')).toEqual(undefined)
305297
```
306298

307299
You may pass several values to `thenReturn` to return different values in succession. If you do not specify `times`, the last value will be latched. Otherwise, each value will be returned the specified number of times.
308300

309301
```ts
310-
const spy = vi.fn()
302+
const mock = when(vi.fn()).calledWith('hello').thenReturn('hi', 'sup?')
311303

312-
when(spy).calledWith('hello').thenReturn('hi', 'sup?')
313-
314-
expect(spy('hello')).toEqual('hi')
315-
expect(spy('hello')).toEqual('sup?')
316-
expect(spy('hello')).toEqual('sup?')
304+
expect(mock('hello')).toEqual('hi')
305+
expect(mock('hello')).toEqual('sup?')
306+
expect(mock('hello')).toEqual('sup?')
317307
```
318308

319-
### `.thenResolve(value: TReturn)`
309+
### `.thenResolve(value: TReturn) -> Mock<TFunc>`
320310

321311
When the stubbing is satisfied, resolve a `Promise` with `value`
322312

323313
```ts
324-
const spy = vi.fn()
325-
326-
when(spy).calledWith('hello').thenResolve('world')
314+
const mock = when(vi.fn()).calledWith('hello').thenResolve('world')
327315

328-
expect(await spy('hello')).toEqual('world')
316+
await expect(mock('hello')).resolves.toEqual('world')
329317
```
330318

331319
To only resolve a value once, use the `times` option.
332320

333321
```ts
334322
import { when } from 'vitest-when'
335323

336-
const spy = vi.fn()
337-
338-
when(spy, { times: 1 }).calledWith('hello').thenResolve('world')
324+
const mock = when(vi.fn(), { times: 1 })
325+
.calledWith('hello')
326+
.thenResolve('world')
339327

340-
expect(await spy('hello')).toEqual('world')
341-
expect(spy('hello')).toEqual(undefined)
328+
await expect(mock('hello')).resolves.toEqual('world')
329+
expect(mock('hello')).toEqual(undefined)
342330
```
343331

344332
You may pass several values to `thenResolve` to resolve different values in succession. If you do not specify `times`, the last value will be latched. Otherwise, each value will be resolved the specified number of times.
345333

346334
```ts
347-
const spy = vi.fn()
348-
349-
when(spy).calledWith('hello').thenResolve('hi', 'sup?')
335+
const mock = when(vi.fn()).calledWith('hello').thenResolve('hi', 'sup?')
350336

351-
expect(await spy('hello')).toEqual('hi')
352-
expect(await spy('hello')).toEqual('sup?')
353-
expect(await spy('hello')).toEqual('sup?')
337+
await expect(mock('hello')).resolves.toEqual('hi')
338+
await expect(mock('hello')).resolves.toEqual('sup?')
339+
await expect(mock('hello')).resolves.toEqual('sup?')
354340
```
355341

356-
### `.thenThrow(error: unknown)`
342+
### `.thenThrow(error: unknown) -> Mock<TFunc>`
357343

358344
When the stubbing is satisfied, throw `error`.
359345

360346
```ts
361-
const spy = vi.fn()
347+
const mock = when(vi.fn()).calledWith('hello').thenThrow(new Error('oh no'))
362348

363-
when(spy).calledWith('hello').thenThrow(new Error('oh no'))
364-
365-
expect(() => spy('hello')).toThrow('oh no')
349+
expect(() => mock('hello')).toThrow('oh no')
366350
```
367351

368352
To only throw an error only once, use the `times` option.
369353

370354
```ts
371355
import { when } from 'vitest-when'
372356

373-
const spy = vi.fn()
374-
375-
when(spy, { times: 1 }).calledWith('hello').thenThrow(new Error('oh no'))
357+
const mock = when(vi.fn(), { times: 1 })
358+
.calledWith('hello')
359+
.thenThrow(new Error('oh no'))
376360

377-
expect(() => spy('hello')).toThrow('oh no')
378-
expect(spy('hello')).toEqual(undefined)
361+
expect(() => mock('hello')).toThrow('oh no')
362+
expect(mock('hello')).toEqual(undefined)
379363
```
380364

381365
You may pass several values to `thenThrow` to throw different errors in succession. If you do not specify `times`, the last value will be latched. Otherwise, each error will be thrown the specified number of times.
382366

383367
```ts
384-
const spy = vi.fn()
385-
386-
when(spy)
368+
const mock = when(vi.fn())
387369
.calledWith('hello')
388370
.thenThrow(new Error('oh no'), new Error('this is bad'))
389371

390-
expect(() => spy('hello')).toThrow('oh no')
391-
expect(() => spy('hello')).toThrow('this is bad')
392-
expect(() => spy('hello')).toThrow('this is bad')
372+
expect(() => mock('hello')).toThrow('oh no')
373+
expect(() => mock('hello')).toThrow('this is bad')
374+
expect(() => mock('hello')).toThrow('this is bad')
393375
```
394376

395-
### `.thenReject(error: unknown)`
377+
### `.thenReject(error: unknown) -> Mock<TFunc>`
396378

397379
When the stubbing is satisfied, reject a `Promise` with `error`.
398380

399381
```ts
400-
const spy = vi.fn()
401-
402-
when(spy).calledWith('hello').thenReject(new Error('oh no'))
382+
const mock = when(vi.fn()).calledWith('hello').thenReject(new Error('oh no'))
403383

404-
await expect(spy('hello')).rejects.toThrow('oh no')
384+
await expect(mock('hello')).rejects.toThrow('oh no')
405385
```
406386

407387
To only throw an error only once, use the `times` option.
408388

409389
```ts
410390
import { times, when } from 'vitest-when'
411391

412-
const spy = vi.fn()
413-
414-
when(spy, { times: 1 }).calledWith('hello').thenReject(new Error('oh no'))
392+
const mock = when(vi.fn(), { times: 1 })
393+
.calledWith('hello')
394+
.thenReject(new Error('oh no'))
415395

416-
await expect(spy('hello')).rejects.toThrow('oh no')
417-
expect(spy('hello')).toEqual(undefined)
396+
await expect(mock('hello')).rejects.toThrow('oh no')
397+
expect(mock('hello')).toEqual(undefined)
418398
```
419399

420400
You may pass several values to `thenReject` to throw different errors in succession. If you do not specify `times`, the last value will be latched. Otherwise, each rejection will be triggered the specified number of times.
421401

422402
```ts
423-
const spy = vi.fn()
424-
425-
when(spy)
403+
const mock = when(vi.fn())
426404
.calledWith('hello')
427405
.thenReject(new Error('oh no'), new Error('this is bad'))
428406

429-
await expect(spy('hello')).rejects.toThrow('oh no')
430-
await expect(spy('hello')).rejects.toThrow('this is bad')
431-
await expect(spy('hello')).rejects.toThrow('this is bad')
407+
await expect(mock('hello')).rejects.toThrow('oh no')
408+
await expect(mock('hello')).rejects.toThrow('this is bad')
409+
await expect(mock('hello')).rejects.toThrow('this is bad')
432410
```
433411

434-
### `.thenDo(callback: (...args: TArgs) => TReturn)`
412+
### `.thenDo(callback: (...args: TArgs) => TReturn) -> Mock<TFunc>`
435413

436414
When the stubbing is satisfied, run `callback` to trigger a side-effect and return its result (if any). `thenDo` is a relatively powerful tool for stubbing complex behaviors, so if you find yourself using `thenDo` often, consider refactoring your code to use more simple interactions! Your future self will thank you.
437415

438416
```ts
439-
const spy = vi.fn()
440417
let called = false
441418

442-
when(spy)
419+
const mock = when(vi.fn())
443420
.calledWith('hello')
444421
.thenDo(() => {
445422
called = true
446423
return 'world'
447424
})
448425

449-
expect(spy('hello')).toEqual('world')
426+
expect(mock('hello')).toEqual('world')
450427
expect(called).toEqual(true)
451428
```
452429

@@ -455,33 +432,29 @@ To only run the callback once, use the `times` option.
455432
```ts
456433
import { times, when } from 'vitest-when'
457434

458-
const spy = vi.fn()
459-
460-
when(spy, { times: 1 })
435+
const mock = when(vi.fn(), { times: 1 })
461436
.calledWith('hello')
462437
.thenDo(() => 'world')
463438

464-
expect(spy('hello')).toEqual('world')
465-
expect(spy('hello')).toEqual(undefined)
439+
expect(mock('hello')).toEqual('world')
440+
expect(mock('hello')).toEqual(undefined)
466441
```
467442

468443
You may pass several callbacks to `thenDo` to trigger different side-effects in succession. If you do not specify `times`, the last callback will be latched. Otherwise, each callback will be triggered the specified number of times.
469444

470445
```ts
471-
const spy = vi.fn()
472-
473-
when(spy)
446+
const mock = when(vi.fn())
474447
.calledWith('hello')
475448
.thenDo(
476449
() => 'world',
477450
() => 'solar system',
478451
)
479452

480-
expect(spy('hello')).toEqual('world')
481-
expect(spy('hello')).toEqual('solar system')
453+
expect(mock('hello')).toEqual('world')
454+
expect(mock('hello')).toEqual('solar system')
482455
```
483456

484-
### `debug(spy: TFunc, options?: DebugOptions): DebugResult`
457+
### `debug(mock: TFunc, options?: DebugOptions): DebugResult`
485458

486459
Logs and returns information about a mock's stubbing and usage. Useful if a test with mocks is failing and you can't figure out why.
487460

0 commit comments

Comments
 (0)