Skip to content

Commit

Permalink
feat(spy): collect mock.contexts (#5955)
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va authored Jun 24, 2024
1 parent 19e9bab commit 3b31a56
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 3 deletions.
19 changes: 17 additions & 2 deletions docs/api/mock.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ fn.mock.settledResults === [

## mock.invocationCallOrder

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

```js
const fn1 = vi.fn()
Expand All @@ -371,9 +371,24 @@ fn1.mock.invocationCallOrder === [1, 3]
fn2.mock.invocationCallOrder === [2]
```

## mock.contexts

This property is an array of `this` values used during each call to the mock function.

```js
const fn = vi.fn()
const context = {}

fn.apply(context)
fn.call(context)

fn.mock.contexts[0] === context
fn.mock.contexts[1] === context
```

## mock.instances

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.
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.

::: warning
If mock was instantiated with `new MyClass()`, then `mock.instances` will be an array with one value:
Expand Down
14 changes: 13 additions & 1 deletion packages/spy/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ export interface MockContext<T extends Procedure> {
* 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.
*/
instances: ReturnType<T>[]
/**
* An array of `this` values that were used during each call to the mock function.
*/
contexts: ThisParameterType<T>[]
/**
* The order of mock's execution. This returns an array of numbers which are shared between all defined mocks.
*
Expand Down Expand Up @@ -431,6 +435,7 @@ function enhanceSpy<T extends Procedure>(
let implementation: T | undefined

let instances: any[] = []
let contexts: any[] = []
let invocations: number[] = []

const state = tinyspy.getInternalState(spy)
Expand All @@ -439,6 +444,9 @@ function enhanceSpy<T extends Procedure>(
get calls() {
return state.calls
},
get contexts() {
return contexts
},
get instances() {
return instances
},
Expand Down Expand Up @@ -469,6 +477,7 @@ function enhanceSpy<T extends Procedure>(

function mockCall(this: unknown, ...args: any) {
instances.push(this)
contexts.push(this)
invocations.push(++callOrder)
const impl = implementationChangedTemporarily
? implementation!
Expand All @@ -490,6 +499,7 @@ function enhanceSpy<T extends Procedure>(
stub.mockClear = () => {
state.reset()
instances = []
contexts = []
invocations = []
return stub
}
Expand Down Expand Up @@ -584,7 +594,9 @@ function enhanceSpy<T extends Procedure>(
export function fn<T extends Procedure = Procedure>(
implementation?: T,
): Mock<T> {
const enhancedSpy = enhanceSpy(tinyspy.internalSpyOn({ spy: implementation || (() => {}) }, 'spy'))
const enhancedSpy = enhanceSpy(tinyspy.internalSpyOn({
spy: implementation || function () {} as T,
}, 'spy'))
if (implementation) {
enhancedSpy.mockImplementation(implementation)
}
Expand Down
29 changes: 29 additions & 0 deletions test/core/test/jest-mock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,35 @@ describe('jest mock compat layer', () => {
expect(Spy.mock.instances).toHaveLength(0)
})

it('collects contexts', () => {
// eslint-disable-next-line prefer-arrow-callback
const Spy = vi.fn(function () {})

expect(Spy.mock.contexts).toHaveLength(0)
const ctx = new Spy()
expect(Spy.mock.contexts).toHaveLength(1)
expect(Spy.mock.contexts[0]).toBe(ctx)

Spy.mockReset()

expect(Spy.mock.contexts).toHaveLength(0)

const ctx2 = {}
Spy.call(ctx2)
expect(Spy.mock.contexts).toHaveLength(1)
expect(Spy.mock.contexts[0]).toBe(ctx2)

Spy.bind(ctx2)()

expect(Spy.mock.contexts).toHaveLength(2)
expect(Spy.mock.contexts[1]).toBe(ctx2)

Spy.apply(ctx2)

expect(Spy.mock.contexts).toHaveLength(3)
expect(Spy.mock.contexts[2]).toBe(ctx2)
})

it('implementation is set correctly on init', () => {
const impl = () => 1
const mock1 = vi.fn(impl)
Expand Down

0 comments on commit 3b31a56

Please sign in to comment.