Skip to content

Conversation

@valentinpalkovic
Copy link
Contributor

@valentinpalkovic valentinpalkovic commented Jul 8, 2025

Closes #31856

What I did

This PR introduces powerful module mocking capabilities directly into Storybook, powered by Vitest's core mocking capabilities. Users can now mock modules, including node modules, local files, and even spy on module functions, with a simple and declarative API. The mocking works seamlessly in both dev mode and production builds, ensuring consistent behavior across all environments.

A new sb.mock() API is exposed and can be used within the .storybook/preview.js file to define global mocks that apply to all stories.

Example Stories

To showcase and test these new capabilities, several new stories have been added under the test category:

  • ModuleMocking: Demonstrates auto-mocking of local modules and dynamic mocking within play functions.
  • ModuleAutoMocking: Shows how the presence of a __mocks__ file automatically replaces a module.
  • ModuleSpyMocking: Illustrates how to spy on original module implementations without fully replacing them.
  • NodeModuleMocking: Provides examples of mocking lodash-es both via __mocks__ and automocking.

🛠️ Implementation Details

The solution leverages @vitest/mocker's core logic but adapts it to Storybook's architecture, particularly to support static builds where a dev server is not available. This is achieved through two new custom Vite plugins.

Dev Mode

In dev mode, mocking relies on Vite's module graph invalidation. When a mock is added, changed, or removed (either in preview.js or the __mocks__ directory), the plugin intelligently invalidates all affected modules and triggers a hot reload. This provides a fast and interactive development experience.

Dev and Build Mode

  1. Build-Time Analysis: A new Vite plugin, viteMockPlugin, scans .storybook/preview.js for all sb.mock() calls during the build process.
  2. Mock Processing:
  • __mocks__ Redirects: If a corresponding file is found in the top-level __mocks__ directory, that file is loaded and transformed by Vite.
  • Automocking & Spies: If no __mocks__ file is found, the original module's code is transformed at build-time to replace its exports with mocks or spies.
  1. No Runtime Overhead: Because all mocking decisions and transformations happen at build time, there is no performance penalty or complex interception logic (like MSW) needed in the final built application. The mocked modules are directly bundled in place of the originals.

🚀 New sb.mock() API

You can now mock any module by calling sb.mock() from your .storybook/preview.js (or .ts) file.

Automocking a Local Module

If a file exists in the top-level __mocks__ directory, it will automatically be used. Otherwise, the module will be automocked, replacing all its exports with mock functions.

// .storybook/preview.js

import { sb } from 'storybook/test';

// Will be replaced by `/path/to/project/__mocks__/my-util.js` if it exists.
sb.mock('../src/my-util.js');

// If no corresponding __mocks__ file exists, all exports from this module
// will be replaced with empty mock functions (e.g., `vi.fn()`).
sb.mock('../src/another-util.js');

Spying on a Module

To keep the original implementation but spy on its function calls, use the `{ spy: true } option. This is useful for asserting that a function was called without altering its behavior.

// .storybook/preview.js
import { sb } from 'storybook/test';

sb.mock('../src/utils/analytics.js', { spy: true });

Mocking Node Modules

You can mock node modules in the same way, including deep imports.

// .storybook/preview.js
import { sb } from 'storybook/test';

// Looks for `__mocks__/lodash-es.js`
sb.mock('lodash-es');

// Looks for `__mocks__/lodash-es/add.js`
sb.mock('lodash-es/add');

// Automocks `sum` because `__mocks__/lodash-es/sum.js` doesn't exist.
sb.mock('lodash-es/sum');

Comparison to Vitest Mocking

While this feature uses Vitest's mocking engine, the implementation within Storybook has some key differences:

  • Scope: Mocks are global and defined only in .storybook/preview.js. Unlike Vitest, you cannot call sb.mock() inside individual story files.
  • Static by Design: All mocking decisions are finalized at build time. This makes the system robust and performant but less dynamic than Vitest's test-by-test mocking capabilities. There is no sb.unmock() or equivalent, as the module graph is fixed in a production build.
  • Runtime Mocking: While the module swap is static, you can still control the behavior of the mocked functions at runtime within a play function or beforeEach hook (e.g., mocked(myFunction).mockReturnValue('new value')).
  • No Factory Functions: The sb.mock() API does not accept a factory function as its second argument (e.g., sb.mock('path', () => ({...}))). This is because all mocking decisions are resolved at build time, whereas factories are executed at runtime.

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This pull request has been released as version 0.0.0-pr-31987-sha-d358bfc5. Try it out in a new sandbox by running npx [email protected] sandbox or in an existing project with npx [email protected] upgrade.

More information
Published version 0.0.0-pr-31987-sha-d358bfc5
Triggered by @valentinpalkovic
Repository storybookjs/storybook
Branch valentin/storybook-mock
Commit d358bfc5
Datetime Fri Jul 18 11:43:23 UTC 2025 (1752839003)
Workflow run 16369687295

To request a new release of this pull request, mention the @storybookjs/core team.

core team members can create a new canary release here or locally with gh workflow run --repo storybookjs/storybook canary-release-pr.yml --field pr=31987

Greptile Summary

Introduces powerful module mocking capabilities to Storybook powered by Vitest's core mocking engine, enabling seamless mocking of both local and node modules during development and production builds.

  • Added new sb.mock() API in code/core/src/test/index.ts for declarative module mocking configuration in preview files
  • Implemented Vite plugins in code/core/src/core-server/presets/vitePlugins for build-time mock analysis and runtime injection
  • Added example stories in code/core/src/test/stories demonstrating auto-mocking, spy-mocking, and node module mocking features
  • Created template system in code/core/templates for injecting mocking runtime code into builds
  • Set up mock directory structure with __mocks__ for overriding module implementations

@nx-cloud
Copy link

nx-cloud bot commented Jul 9, 2025

View your CI Pipeline Execution ↗ for commit 7f7556c

Command Status Duration Result
nx run-many -t build --parallel=3 ✅ Succeeded 1m 15s View ↗

☁️ Nx Cloud last updated this comment at 2025-07-18 14:01:26 UTC

@valentinpalkovic valentinpalkovic force-pushed the valentin/storybook-mock branch from c3f853f to 74b1ea4 Compare July 9, 2025 08:20
@storybook-bot
Copy link
Contributor

Failed to publish canary version of this pull request, triggered by @valentinpalkovic. See the failed workflow run at: https://github.com/storybookjs/storybook/actions/runs/16170019992

@storybook-bot
Copy link
Contributor

Failed to publish canary version of this pull request, triggered by @valentinpalkovic. See the failed workflow run at: https://github.com/storybookjs/storybook/actions/runs/16170625921

@storybook-app-bot
Copy link

storybook-app-bot bot commented Jul 9, 2025

Package Benchmarks

Commit: 7f7556c, ran on 18 July 2025 at 13:50:32 UTC

The following packages have significant changes to their size or dependencies:

storybook

Before After Difference
Dependency count 51 56 🚨 +5 🚨
Self size 31.77 MB 41.35 MB 🚨 +9.58 MB 🚨
Dependency size 17.43 MB 18.20 MB 🚨 +762 KB 🚨
Bundle Size Analyzer Link Link

@storybook/vue3-vite

Before After Difference
Dependency count 104 105 🚨 +1 🚨
Self size 34 KB 34 KB 0 B
Dependency size 42.73 MB 42.78 MB 🚨 +51 KB 🚨
Bundle Size Analyzer Link Link

sb

Before After Difference
Dependency count 52 57 🚨 +5 🚨
Self size 1 KB 1 KB 0 B
Dependency size 49.20 MB 59.54 MB 🚨 +10.34 MB 🚨
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 217 221 🚨 +4 🚨
Self size 582 KB 584 KB 🚨 +2 KB 🚨
Dependency size 93.68 MB 103.94 MB 🚨 +10.25 MB 🚨
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 186 190 🚨 +4 🚨
Self size 31 KB 31 KB 0 B
Dependency size 77.77 MB 88.03 MB 🚨 +10.25 MB 🚨
Bundle Size Analyzer Link Link

@valentinpalkovic valentinpalkovic marked this pull request as ready for review July 10, 2025 08:23
@valentinpalkovic valentinpalkovic marked this pull request as draft July 10, 2025 08:23
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

30 files reviewed, 28 comments
Edit PR Review Bot Settings | Greptile

@valentinpalkovic valentinpalkovic force-pushed the valentin/storybook-mock branch from 9b31466 to 5b4f063 Compare July 16, 2025 12:49
@ndelangen
Copy link
Member

@valentinpalkovic please add a manual testing section, so we can do QA

@yannbf yannbf removed the needs qa Indicates that this needs manual QA during the upcoming minor/major release label Nov 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:daily Run the CI jobs that normally run in the daily job. feature request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Tracking]: Module automocking preview

6 participants