-
-
Notifications
You must be signed in to change notification settings - Fork 9.9k
Test: Fix clearing mocks in Vitest #34078
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
base: next
Are you sure you want to change the base?
Changes from all commits
07e1996
8bba443
114a39d
eefb6f2
99a1bfb
c80ec89
1580101
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,19 @@ export type * from '@vitest/spy'; | |
|
|
||
| export { isMockFunction, mocks }; | ||
|
|
||
| /** | ||
| * Global registry for module mock spies created by `sb.mock('...', { spy: true })`. | ||
| * | ||
| * These spies are created by the module mocker (via `__vitest_mocker__.mockObject()`) and may use a | ||
| * different `@vitest/spy` instance than the one bundled with storybook/test. This means they won't | ||
| * appear in the `mocks` Set that `clearAllMocks`/`resetAllMocks`/`restoreAllMocks` iterate over. | ||
| * | ||
| * The automock code generation registers spies here so they can be properly cleared between | ||
| * stories. | ||
| */ | ||
| const moduleMockSpies: Set<MockInstance> = ((globalThis as any).__STORYBOOK_MODULE_MOCK_SPIES__ ??= | ||
| new Set<MockInstance>()); | ||
|
|
||
| type Listener = (mock: MockInstance, args: unknown[]) => void; | ||
| const listeners = new Set<Listener>(); | ||
|
|
||
|
|
@@ -63,6 +76,7 @@ function listenWhenCalled(mock: MockInstance) { | |
| */ | ||
| export function clearAllMocks() { | ||
| mocks.forEach((spy) => spy.mockClear()); | ||
| moduleMockSpies.forEach((spy) => spy.mockClear()); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -74,6 +88,7 @@ export function clearAllMocks() { | |
| */ | ||
| export function resetAllMocks() { | ||
| mocks.forEach((spy) => spy.mockReset()); | ||
| moduleMockSpies.forEach((spy) => spy.mockReset()); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -82,6 +97,11 @@ export function resetAllMocks() { | |
| */ | ||
| export function restoreAllMocks() { | ||
| mocks.forEach((spy) => spy.mockRestore()); | ||
| // For module mock spies, we only clear call history (not restore), because: | ||
| // - mockRestore() would try to undo the spyOn on the module export object, which is not | ||
| // meaningful for automocked modules where the spy reference is captured at module load time | ||
| // - The spy needs to remain active for subsequent stories | ||
| moduleMockSpies.forEach((spy) => spy.mockClear()); | ||
|
Comment on lines
98
to
+104
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result:
Practical workaround (keeps the same spy reference, but does not truly restore):
Citations:
🏁 Script executed: cd code/core/src && find . -name "spy.ts" -o -name "spy.js" | head -5Repository: storybookjs/storybook Length of output: 78 🏁 Script executed: cat -n code/core/src/test/spy.ts | head -120Repository: storybookjs/storybook Length of output: 5438 🏁 Script executed: cat -n code/core/src/test/spy.ts | sed -n '85,115p'Repository: storybookjs/storybook Length of output: 1579 🏁 Script executed: cd code && rg '__STORYBOOK_MODULE_MOCK_SPIES__' --type ts --type js -B2 -A2Repository: storybookjs/storybook Length of output: 1189 🏁 Script executed: cd code && rg "moduleMockSpies" --type ts --type js -B2 -A2Repository: storybookjs/storybook Length of output: 1118 🏁 Script executed: cd code && rg "vi\.mock\(" --type ts --type js --max-count=10 | head -20Repository: storybookjs/storybook Length of output: 1854 🏁 Script executed: cd code/core/src/mocking-utils && cat -n automock.ts | head -80Repository: storybookjs/storybook Length of output: 3883 🏁 Script executed: cd code/core/src/mocking-utils && cat -n automock.ts | sed -n '100,180p'Repository: storybookjs/storybook Length of output: 4023 🏁 Script executed: cd code/core/src/mocking-utils && cat -n automock.ts | sed -n '175,200p'Repository: storybookjs/storybook Length of output: 1377 🏁 Script executed: cd code && rg "mockImplementation\|mockResolvedValue" --type ts --type tsx -c | head -10Repository: storybookjs/storybook Length of output: 92 🏁 Script executed: cd code && rg "mockImplementation|mockResolvedValue" --type ts -c | head -10Repository: storybookjs/storybook Length of output: 593 🏁 Script executed: cd code && rg "mockReset" --type ts -A3 -B1 | head -40Repository: storybookjs/storybook Length of output: 2448
Calling Consider using 🧰 Tools🪛 Biome (2.4.4)[error] 99-99: This callback passed to forEach() iterable method should not return a value. (lint/suspicious/useIterableCallbackReturn) [error] 104-104: This callback passed to forEach() iterable method should not return a value. (lint/suspicious/useIterableCallbackReturn) 🤖 Prompt for AI Agents
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @valentinpalkovic can you verify? |
||
| } | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| export type Data = { | ||
| userId: number; | ||
| id: number; | ||
| title: string; | ||
| body: string; | ||
| }; | ||
|
|
||
| export const fetchData = async (): Promise<Data[]> => { | ||
| return Promise.resolve([ | ||
| { | ||
| userId: 1, | ||
| id: 1, | ||
| title: 'mocked title', | ||
| body: 'mocked body', | ||
| }, | ||
| ]); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| // Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. | ||
| import { global as globalThis } from '@storybook/global'; | ||
|
|
||
| import { clearAllMocks, expect, waitFor } from 'storybook/test'; | ||
|
|
||
| import { fetchData } from './ClearModuleMocksMocking.api'; | ||
|
|
||
| /** | ||
| * The purpose of this story is to verify that the `clearAllMocks` function properly clears mocks | ||
| * created with the `spy: true` option in `sb.mock()`. This is necessary because those mocks are | ||
| * created with a different instance of `@vitest/spy` than the one bundled with storybook/test. This | ||
| * means they won't be cleared by the `clearMocks` option of Vitest, and we need to use | ||
| * `clearAllMocks` to clear them manually. See issue: | ||
| * https://github.com/storybookjs/storybook/issues/34075 | ||
| */ | ||
| const meta = { | ||
| component: globalThis.__TEMPLATE_COMPONENTS__.Button, | ||
| args: { | ||
| label: 'Fetch Data', | ||
| onClick: () => { | ||
| fetchData(); | ||
| }, | ||
| }, | ||
| beforeEach: async () => { | ||
| clearAllMocks(); | ||
| }, | ||
| }; | ||
|
|
||
| export default meta; | ||
|
|
||
| export const First = { | ||
| args: {}, | ||
| play: async ({ canvas }: any) => { | ||
| const button = await canvas.getByRole('button'); | ||
| await button.click(); | ||
| await waitFor(() => { | ||
| expect(fetchData).toHaveBeenCalledTimes(1); | ||
| }); | ||
| }, | ||
| }; | ||
|
|
||
| export const Second = { | ||
| args: {}, | ||
| play: async ({ canvas }: any) => { | ||
| const button = await canvas.getByRole('button'); | ||
| await button.click(); | ||
| await waitFor(() => { | ||
| expect(fetchData).toHaveBeenCalledTimes(1); | ||
| }); | ||
| }, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These
forEachcallbacks trip Biome's iterable-callback-return rule.Biome is already flagging these new lines because the concise arrows return the mock instance expression. Use block bodies so the callbacks return
void.🧹 Proposed fix
Also applies to: 91-91, 104-104
🧰 Tools
🪛 Biome (2.4.4)
[error] 79-79: This callback passed to forEach() iterable method should not return a value.
(lint/suspicious/useIterableCallbackReturn)
🤖 Prompt for AI Agents