Skip to content

Commit 3b31a56

Browse files
authored
feat(spy): collect mock.contexts (#5955)
1 parent 19e9bab commit 3b31a56

File tree

3 files changed

+59
-3
lines changed

3 files changed

+59
-3
lines changed

docs/api/mock.md

+17-2
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ fn.mock.settledResults === [
357357

358358
## mock.invocationCallOrder
359359

360-
The order of mock's execution. This returns an array of numbers that are shared between all defined mocks.
360+
This property returns the order of the mock function's execution. It is an array of numbers that are shared between all defined mocks.
361361

362362
```js
363363
const fn1 = vi.fn()
@@ -371,9 +371,24 @@ fn1.mock.invocationCallOrder === [1, 3]
371371
fn2.mock.invocationCallOrder === [2]
372372
```
373373

374+
## mock.contexts
375+
376+
This property is an array of `this` values used during each call to the mock function.
377+
378+
```js
379+
const fn = vi.fn()
380+
const context = {}
381+
382+
fn.apply(context)
383+
fn.call(context)
384+
385+
fn.mock.contexts[0] === context
386+
fn.mock.contexts[1] === context
387+
```
388+
374389
## mock.instances
375390

376-
This is an array containing all instances that were instantiated when mock was called with a `new` keyword. Note that this is an actual context (`this`) of the function, not a return value.
391+
This property is an array containing all instances that were created when the mock was called with the `new` keyword. Note that this is an actual context (`this`) of the function, not a return value.
377392

378393
::: warning
379394
If mock was instantiated with `new MyClass()`, then `mock.instances` will be an array with one value:

packages/spy/src/index.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ export interface MockContext<T extends Procedure> {
5858
* This is an array containing all instances that were instantiated when mock was called with a `new` keyword. Note that this is an actual context (`this`) of the function, not a return value.
5959
*/
6060
instances: ReturnType<T>[]
61+
/**
62+
* An array of `this` values that were used during each call to the mock function.
63+
*/
64+
contexts: ThisParameterType<T>[]
6165
/**
6266
* The order of mock's execution. This returns an array of numbers which are shared between all defined mocks.
6367
*
@@ -431,6 +435,7 @@ function enhanceSpy<T extends Procedure>(
431435
let implementation: T | undefined
432436

433437
let instances: any[] = []
438+
let contexts: any[] = []
434439
let invocations: number[] = []
435440

436441
const state = tinyspy.getInternalState(spy)
@@ -439,6 +444,9 @@ function enhanceSpy<T extends Procedure>(
439444
get calls() {
440445
return state.calls
441446
},
447+
get contexts() {
448+
return contexts
449+
},
442450
get instances() {
443451
return instances
444452
},
@@ -469,6 +477,7 @@ function enhanceSpy<T extends Procedure>(
469477

470478
function mockCall(this: unknown, ...args: any) {
471479
instances.push(this)
480+
contexts.push(this)
472481
invocations.push(++callOrder)
473482
const impl = implementationChangedTemporarily
474483
? implementation!
@@ -490,6 +499,7 @@ function enhanceSpy<T extends Procedure>(
490499
stub.mockClear = () => {
491500
state.reset()
492501
instances = []
502+
contexts = []
493503
invocations = []
494504
return stub
495505
}
@@ -584,7 +594,9 @@ function enhanceSpy<T extends Procedure>(
584594
export function fn<T extends Procedure = Procedure>(
585595
implementation?: T,
586596
): Mock<T> {
587-
const enhancedSpy = enhanceSpy(tinyspy.internalSpyOn({ spy: implementation || (() => {}) }, 'spy'))
597+
const enhancedSpy = enhanceSpy(tinyspy.internalSpyOn({
598+
spy: implementation || function () {} as T,
599+
}, 'spy'))
588600
if (implementation) {
589601
enhancedSpy.mockImplementation(implementation)
590602
}

test/core/test/jest-mock.test.ts

+29
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,35 @@ describe('jest mock compat layer', () => {
4343
expect(Spy.mock.instances).toHaveLength(0)
4444
})
4545

46+
it('collects contexts', () => {
47+
// eslint-disable-next-line prefer-arrow-callback
48+
const Spy = vi.fn(function () {})
49+
50+
expect(Spy.mock.contexts).toHaveLength(0)
51+
const ctx = new Spy()
52+
expect(Spy.mock.contexts).toHaveLength(1)
53+
expect(Spy.mock.contexts[0]).toBe(ctx)
54+
55+
Spy.mockReset()
56+
57+
expect(Spy.mock.contexts).toHaveLength(0)
58+
59+
const ctx2 = {}
60+
Spy.call(ctx2)
61+
expect(Spy.mock.contexts).toHaveLength(1)
62+
expect(Spy.mock.contexts[0]).toBe(ctx2)
63+
64+
Spy.bind(ctx2)()
65+
66+
expect(Spy.mock.contexts).toHaveLength(2)
67+
expect(Spy.mock.contexts[1]).toBe(ctx2)
68+
69+
Spy.apply(ctx2)
70+
71+
expect(Spy.mock.contexts).toHaveLength(3)
72+
expect(Spy.mock.contexts[2]).toBe(ctx2)
73+
})
74+
4675
it('implementation is set correctly on init', () => {
4776
const impl = () => 1
4877
const mock1 = vi.fn(impl)

0 commit comments

Comments
 (0)