diff --git a/packages/vitest/src/runtime/moduleRunner/moduleRunner.ts b/packages/vitest/src/runtime/moduleRunner/moduleRunner.ts index be655b57f8af..c81b5ee89aee 100644 --- a/packages/vitest/src/runtime/moduleRunner/moduleRunner.ts +++ b/packages/vitest/src/runtime/moduleRunner/moduleRunner.ts @@ -168,23 +168,29 @@ export class VitestModuleRunner if (mod.meta && 'mockedModule' in mod.meta) { const mockedModule = mod.meta.mockedModule as MockedModule const mockId = this.mocker.getMockPath(mod.id) + const currentMock = this.mocker.getDependencyMock(mod.id) // bypass mock and force "importActual" behavior when: // - mock was removed by doUnmock (stale mockedModule in meta) + if (!currentMock) { + const node = await this.fetchModule(injectQuery(url, '_vitest_original')) + return this._cachedRequest(node.url, node, callstack, metadata) + } // - self-import: mock factory/file is importing the module it's mocking - const isStale = !this.mocker.getDependencyMock(mod.id) const isSelfImport = callstack.includes(mockId) || callstack.includes(url) - || ('redirect' in mockedModule && callstack.includes(mockedModule.redirect)) - if (isStale || isSelfImport) { + || ('redirect' in currentMock && callstack.includes(currentMock.redirect)) + if (isSelfImport) { const node = await this.fetchModule(injectQuery(url, '_vitest_original')) return this._cachedRequest(node.url, node, callstack, metadata) } - mocked = await this.mocker.requestWithMockedModule( - url, - mod, - callstack, - mockedModule, - ) + const isAutoMock = currentMock.type === 'automock' || currentMock.type === 'autospy' + if (isAutoMock && currentMock !== mockedModule) { + const freshNode = await this.fetchModule(injectQuery(url, '_vitest_original')) + mocked = await this.mocker.requestWithMockedModule(url, freshNode, callstack, currentMock) + } + else { + mocked = await this.mocker.requestWithMockedModule(url, mod, callstack, currentMock) + } } else { mocked = await this.mocker.mockedRequest(url, mod, callstack) diff --git a/test/e2e/test/mocking.test.ts b/test/e2e/test/mocking.test.ts index fe2e55bc7d4a..f99717878a73 100644 --- a/test/e2e/test/mocking.test.ts +++ b/test/e2e/test/mocking.test.ts @@ -3,7 +3,7 @@ import path from 'node:path' import { playwright } from '@vitest/browser-playwright' import { expect, test } from 'vitest' import { rolldownVersion } from 'vitest/node' -import { runInlineTests, runVitest } from '../../test-utils' +import { runInlineTests, runVitest, StableTestFileOrderSorter } from '../../test-utils' test('setting resetMocks works if restoreMocks is also set', async () => { const { stderr, testTree } = await runInlineTests({ @@ -510,3 +510,65 @@ test("local", async () => { } `) }) + +test('automocking works with isolate:false when factory mock runs first (resolve alias)', async () => { + const { stderr, testTree } = await runInlineTests({ + 'vitest.config.js': ` +import path from 'node:path' +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + resolve: { + alias: { + '~': path.resolve(import.meta.dirname, 'src'), + }, + }, + test: { + isolate: false, + }, +}) + `, + './src/dep.ts': ` +export function useDep(): string { return 'real' } +export function helperDep(): number { return 42 } + `, + './a-factory.test.ts': ` +import { vi, test, expect } from 'vitest' +import { useDep } from '~/dep' +vi.mock(import('~/dep'), () => ({ + useDep: () => 'factory', + helperDep: () => 0, +})) +test('factory mock', () => { + expect(useDep()).toBe('factory') +}) + `, + './b-automock.test.ts': ` +import { vi, test, expect } from 'vitest' +import { useDep } from '~/dep' +vi.mock(import('~/dep')) +test('automock exports are mock functions', () => { + expect(vi.isMockFunction(useDep)).toBe(true) +}) +test('automock mockReturnValue works', () => { + vi.mocked(useDep).mockReturnValue('mocked') + expect(useDep()).toBe('mocked') +}) + `, + }, { + sequence: { sequencer: StableTestFileOrderSorter }, + }) + + expect(stderr).toBe('') + expect(testTree()).toMatchInlineSnapshot(` + { + "a-factory.test.ts": { + "factory mock": "passed", + }, + "b-automock.test.ts": { + "automock exports are mock functions": "passed", + "automock mockReturnValue works": "passed", + }, + } + `) +})