Skip to content

feat(mock): support spy: true option in rs.mock()#901

Merged
9aoy merged 2 commits intomainfrom
mockSpy
Jan 26, 2026
Merged

feat(mock): support spy: true option in rs.mock()#901
9aoy merged 2 commits intomainfrom
mockSpy

Conversation

@9aoy
Copy link
Copy Markdown
Collaborator

@9aoy 9aoy commented Jan 23, 2026

Summary

Support the { spy: true } option in rs.mock() and related APIs, allowing modules to be auto-mocked while preserving their original implementations.

import { add, multiply } from './math';

rs.mock('./math', { spy: true });

test('spy mode preserves implementation', () => {
  expect(add(1, 2)).toBe(3); // Original works
  expect(add).toHaveBeenCalledWith(1, 2); // Can assert calls
  
  // Can also override with mockImplementation/mockReturnValue
  rs.mocked(add).mockReturnValueOnce(100);
  expect(add(1, 2)).toBe(100);
  expect(add(1, 2)).toBe(3); // Back to original
});

Features

  • Add { spy: true } option to rs.mock, rs.doMock, rs.mockRequire, rs.doMockRequire
  • When spy: true, all module exports are wrapped in spy functions that:
    • Track calls (can use toHaveBeenCalled, toHaveBeenCalledWith, etc.)
    • Still execute the original implementation
  • Add MockModuleOptions type with literal { spy: true } (throws error for invalid values)

Changes

  • packages/core/src/types/mock.ts: Add MockModuleOptions type
  • packages/core/src/core/plugins/mockRuntimeCode.js: Handle { spy: true } option in 4 mock functions
  • e2e/mock/tests/mockSpy.test.ts: E2E tests for spy mode
  • e2e/mock/tests/doMockSpy.test.ts: E2E tests for doMock with spy
  • website/docs/*/api/runtime-api/rstest/mock-modules.mdx: Documentation updates
  • website/docs/*/api/runtime-api/rstest/mock-functions.mdx: Documentation updates
  • website/AGENTS.md: Add heading-case guideline

- Add `{ spy: true }` option to `rs.mock`, `rs.doMock`, `rs.mockRequire`, `rs.doMockRequire`
- When `spy: true`, module is auto-mocked but original implementations are preserved
- All exports are wrapped in spy functions that track calls while executing original code
- Add `MockModuleOptions` type with literal `{ spy: true }` (throws error for other values)
- Add e2e tests for spy mode with mockImplementation, mockReturnValue, unmock
- Update documentation with examples for factory function, rs.fn(), rs.mockObject(), and partial mock
Copilot AI review requested due to automatic review settings January 23, 2026 07:58
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Jan 23, 2026

Deploying rstest with  Cloudflare Pages  Cloudflare Pages

Latest commit: 3eb17ff
Status: ✅  Deploy successful!
Preview URL: https://6079ae64.rstest.pages.dev
Branch Preview URL: https://mockspy.rstest.pages.dev

View logs

@9aoy 9aoy changed the title feat(mock): support rs.mock('module', { spy: true }) option feat(mock): support spy: true option in rs.mock() Jan 23, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds { spy: true } support to rs.mock()-family APIs so modules can be auto-mocked while preserving original implementations, with documentation and e2e coverage.

Changes:

  • Extend core typings to accept { spy: true } for module mocking APIs.
  • Implement spy-mode handling in the webpack runtime mock hooks.
  • Add e2e tests and update EN/ZH docs for the new spy behavior.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/core/src/types/mock.ts Introduces MockModuleOptions and updates rs.mock/doMock/mockRequire/doMockRequire signatures to accept { spy: true }.
packages/core/src/core/plugins/mockRuntimeCode.js Adds runtime support for { spy: true } across 4 mock entry points by wrapping original exports via mockObject(..., { spy: true }).
e2e/mock/tests/mockSpy.test.ts Adds e2e coverage for rs.mock(..., { spy: true }) behavior (call tracking + preserving implementation).
e2e/mock/tests/doMockSpy.test.ts Adds e2e coverage for rs.doMock(..., { spy: true }) behavior.
website/docs/en/api/runtime-api/rstest/mock-modules.mdx Documents { spy: true } usage and reorganizes module mocking docs.
website/docs/zh/api/runtime-api/rstest/mock-modules.mdx Same documentation updates for ZH.
website/docs/en/api/runtime-api/rstest/mock-functions.mdx Updates examples to use { spy: true } in the module-mocking context.
website/docs/zh/api/runtime-api/rstest/mock-functions.mdx Same example update for ZH.
website/AGENTS.md Adds sentence-style heading capitalization guideline.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +91 to +95
__webpack_require__.r(__webpack_exports__);
for (const key in mockedModule) {
__webpack_require__.d(__webpack_exports__, {
[key]: () => mockedModule[key],
});
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

rs.mock(..., { spy: true }) marks the replacement as an ESM module (__webpack_require__.r(__webpack_exports__)) and only re-exports enumerable keys from mockedModule. For CommonJS targets (where consumers use default-import interop via __webpack_require__.n), this can break import x from 'cjs-module' because there is no default export defined. Consider adding a default export (e.g., mapping to the spied module) when the original module is not __esModule, to preserve default-import behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +79
rs.mocked(increment).mockReturnValueOnce(111);
rs.mocked(increment).mockReturnValueOnce(222);

expect(increment(1)).toBe(111);
expect(increment(1)).toBe(222);
// Falls back to previous mockReturnValue(999)
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

This test is order-dependent: it expects the third call to fall back to a mockReturnValue(999) configured in a previous test. If tests are reordered or run with isolation, this can become flaky. Set the baseline mockReturnValue within this test (or reset+configure in a beforeEach) so the test is self-contained.

Suggested change
rs.mocked(increment).mockReturnValueOnce(111);
rs.mocked(increment).mockReturnValueOnce(222);
expect(increment(1)).toBe(111);
expect(increment(1)).toBe(222);
// Falls back to previous mockReturnValue(999)
rs.mocked(increment).mockReturnValue(999);
rs.mocked(increment).mockReturnValueOnce(111);
rs.mocked(increment).mockReturnValueOnce(222);
expect(increment(1)).toBe(111);
expect(increment(1)).toBe(222);
// Falls back to baseline mockReturnValue(999) set in this test

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +17
expect(increment).toHaveBeenCalledTimes(2);
expect(increment).toHaveBeenCalledWith(1);
expect(increment).toHaveBeenCalledWith(5);
expect(rs.isMockFunction(increment)).toBe(true);
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

These assertions can be flaky if this file’s tests run in a different order: rs.doMock(..., { spy: true }) may reuse an existing mock function (mockObject returns existing _isMockFunction values as-is), so call history can carry over between tests and make toHaveBeenCalledTimes(2) fail. Consider resetting mocks/modules (e.g., rs.resetAllMocks() / rs.resetModules() / rs.doUnmock(...)) in beforeEach/afterEach to keep tests isolated.

Copilot uses AI. Check for mistakes.
@9aoy 9aoy merged commit 5a769dd into main Jan 26, 2026
10 checks passed
@9aoy 9aoy deleted the mockSpy branch January 26, 2026 09:04
@9aoy 9aoy mentioned this pull request Jan 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants