Skip to content

feat(test): support unstubbing actions #2960

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: v3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/docs/cookbook/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,33 @@ store.someAction()
expect(store.someAction).toHaveBeenCalledTimes(1)
```

If you want to normally execute only some of the actions with `stubActions: true` you can reset them with `mockRestore`:

```js
const wrapper = mount(Counter, {
global: {
// stubActions is set to 'true' by default
plugins: [createTestingPinia()],
},
})

const store = useSomeStore()

// All subsequent calls of this action WILL execute the implementation defined by the store
store.someAction.mockRestore()

// Runs the actual implementation
store.someAction()

// Runs the mocked function
store.anotherAction()

// the spy is kept on unmocked functions as well
expect(store.someAction).toHaveBeenCalledTimes(1)
```

Note: this behaviour is supported only when using either jest or vitest.

### Mocking the returned value of an action

Actions are automatically spied but type-wise, they are still the regular actions. In order to get the correct type, we must implement a custom type-wrapper that applies the `Mock` type to each action. **This type depends on the testing framework you are using**. Here is an example with Vitest:
Expand Down
27 changes: 27 additions & 0 deletions packages/testing/src/testing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,33 @@ describe('Testing', () => {
expect(counter.increment).toHaveBeenCalledTimes(4)
expect(counter.increment).toHaveBeenLastCalledWith(10)
})

it(`can unstub actions with ${name}`, () => {
const { counter, wrapper } = factory(undefined, useStore)

// @ts-ignore
counter.increment.mockRestore()

counter.increment()
expect(counter.n).toBe(1)
expect(counter.increment).toHaveBeenCalledTimes(1)
expect(counter.increment).toHaveBeenLastCalledWith()

counter.increment(5)
expect(counter.n).toBe(6)
expect(counter.increment).toHaveBeenCalledTimes(2)
expect(counter.increment).toHaveBeenLastCalledWith(5)

wrapper.findAll('button')[0].trigger('click')
expect(counter.n).toBe(7)
expect(counter.increment).toHaveBeenCalledTimes(3)
expect(counter.increment).toHaveBeenLastCalledWith()

wrapper.findAll('button')[1].trigger('click')
expect(counter.n).toBe(17)
expect(counter.increment).toHaveBeenCalledTimes(4)
expect(counter.increment).toHaveBeenLastCalledWith(10)
})
})
})

Expand Down
29 changes: 24 additions & 5 deletions packages/testing/src/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import {
// while the other types hide internal properties
import type { ComputedRefImpl } from '@vue/reactivity'

type MockFn = ((...args: any[]) => any) & {
mockReturnValue: (value: any) => MockFn
}

export interface TestingOptions {
/**
* Allows defining a partial initial state of all your stores. This state gets applied after a store is created,
Expand Down Expand Up @@ -61,7 +65,7 @@ export interface TestingOptions {
* with `jest.fn` in Jest projects or `vi.fn` in Vitest projects if
* `globals: true` is set.
*/
createSpy?: (fn?: (...args: any[]) => any) => (...args: any[]) => any
createSpy?: (fn?: (...args: any[]) => any) => MockFn
}

/**
Expand All @@ -76,7 +80,7 @@ export interface TestingPinia extends Pinia {
declare var vi:
| undefined
| {
fn: (fn?: (...args: any[]) => any) => (...args: any[]) => any
fn: (fn?: (...args: any[]) => any) => MockFn
}

/**
Expand Down Expand Up @@ -135,15 +139,30 @@ export function createTestingPinia({
)
}

const trySpyWithMock = (fn: any) => {
try {
// user provided createSpy might not have a mockReturnValue similar to jest and vitest
return createSpy(fn).mockReturnValue(undefined)
} catch (e) {
return createSpy()
}
}

// stub actions
pinia._p.push(({ store, options }) => {
Object.keys(options.actions).forEach((action) => {
if (action === '$reset') return
store[action] = stubActions ? createSpy() : createSpy(store[action])
store[action] = stubActions
? trySpyWithMock(store[action])
: createSpy(store[action])
})

store.$patch = stubPatch ? createSpy() : createSpy(store.$patch)
store.$reset = stubReset ? createSpy() : createSpy(store.$reset)
store.$patch = stubPatch
? trySpyWithMock(store.$patch)
: createSpy(store.$patch)
store.$reset = stubReset
? trySpyWithMock(store.$reset)
: createSpy(store.$reset)
})

if (fakeApp) {
Expand Down