From fd8292acfb69a2fef5a92b327bb6b47be2e450d9 Mon Sep 17 00:00:00 2001 From: Daniel Tschinder <231804+danez@users.noreply.github.com> Date: Fri, 2 Dec 2022 13:54:44 +0100 Subject: [PATCH] fix(mocker): set cache before mocking to allow circular dependencies (#2391) --- packages/vitest/src/runtime/mocker.ts | 14 ++++++++------ test/core/test/mocked-circular.test.ts | 12 ++++++++++++ 2 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 test/core/test/mocked-circular.test.ts diff --git a/packages/vitest/src/runtime/mocker.ts b/packages/vitest/src/runtime/mocker.ts index c6f501861536..bad8604c308c 100644 --- a/packages/vitest/src/runtime/mocker.ts +++ b/packages/vitest/src/runtime/mocker.ts @@ -1,7 +1,7 @@ import { existsSync, readdirSync } from 'fs' import { isNodeBuiltin } from 'mlly' import { basename, dirname, extname, join, resolve } from 'pathe' -import { normalizeRequestId, pathFromRoot, toFilePath } from 'vite-node/utils' +import { normalizeRequestId, pathFromRoot } from 'vite-node/utils' import type { ModuleCacheMap } from 'vite-node/client' import { getAllMockableProperties, getType, getWorkerState, mergeSlashes, slash } from '../utils' import { distDir } from '../constants' @@ -195,7 +195,7 @@ export class VitestMocker { return existsSync(fullPath) ? fullPath : null } - public mockObject(object: Record) { + public mockObject(object: Record, mockExports: Record = {}) { if (!VitestMocker.spyModule) { throw new Error( 'Error: Spy module is not defined. ' @@ -275,7 +275,7 @@ export class VitestMocker { } } - const mockedObject: Record = {} + const mockedObject: Record = mockExports mockPropertiesOf(object, mockedObject) // Plug together refs @@ -360,10 +360,12 @@ export class VitestMocker { const cache = this.moduleCache.get(mockPath) if (cache?.exports) return cache.exports - const cacheKey = toFilePath(dep, this.root) - const mod = this.moduleCache.get(cacheKey)?.exports || await this.request(dep) - const exports = this.mockObject(mod) + + const exports = {} + // Assign the empty exports object early to allow for cycles to work. The object will be filled by mockObject() this.moduleCache.set(mockPath, { exports }) + const mod = await this.request(dep) + this.mockObject(mod, exports) return exports } if (typeof mock === 'function' && !callstack.includes(mockPath)) { diff --git a/test/core/test/mocked-circular.test.ts b/test/core/test/mocked-circular.test.ts new file mode 100644 index 000000000000..e6f7b397232b --- /dev/null +++ b/test/core/test/mocked-circular.test.ts @@ -0,0 +1,12 @@ +import { expect, it, vi } from 'vitest' +// The order of the two imports here matters: B before A +import { circularB } from '../src/circularB' +import { circularA } from '../src/circularA' + +vi.mock('../src/circularB') + +it('circular', () => { + circularA() + + expect(circularB).toHaveBeenCalledOnce() +})