diff --git a/packages/docs/cookbook/testing.md b/packages/docs/cookbook/testing.md index 3eab75d949..5dcebd0c90 100644 --- a/packages/docs/cookbook/testing.md +++ b/packages/docs/cookbook/testing.md @@ -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: diff --git a/packages/testing/src/testing.spec.ts b/packages/testing/src/testing.spec.ts index 75b40687b9..c80285e44b 100644 --- a/packages/testing/src/testing.spec.ts +++ b/packages/testing/src/testing.spec.ts @@ -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) + }) }) }) diff --git a/packages/testing/src/testing.ts b/packages/testing/src/testing.ts index 8be334b7e0..2b24aa619d 100644 --- a/packages/testing/src/testing.ts +++ b/packages/testing/src/testing.ts @@ -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, @@ -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 } /** @@ -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 } /** @@ -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) {