diff --git a/examples/mocks/test/error-mock.spec.ts b/examples/mocks/test/error-mock.spec.ts new file mode 100644 index 000000000000..4ccbf610ce7f --- /dev/null +++ b/examples/mocks/test/error-mock.spec.ts @@ -0,0 +1,8 @@ +vi.mock('../src/default', () => { + throw new Error('some error') +}) + +test('when using top level variable, gives helpful message', async () => { + await expect(() => import('../src/default').then(m => m.default)).rejects + .toThrowErrorMatchingInlineSnapshot('"[vitest] There was an error, when mocking a module. If you are using vi.mock, make sure you are not using top level variables inside, since this call is hoisted. Read more: https://vitest.dev/api/#vi-mock"') +}) diff --git a/examples/mocks/test/factory.test.ts b/examples/mocks/test/factory.test.ts index ea70fceadd83..e2188d9303c8 100644 --- a/examples/mocks/test/factory.test.ts +++ b/examples/mocks/test/factory.test.ts @@ -48,11 +48,11 @@ vi.mock('../src/default.ts', () => null) describe('mocking with factory', () => { test('missing exports on mock', () => { - expect(() => example.default).toThrowError('[vitest] No "default" export is defined on the "mock:/src/example.ts"') - expect(() => example.boolean).toThrowError('[vitest] No "boolean" export is defined on the "mock:/src/example.ts"') - expect(() => example.object).toThrowError('[vitest] No "object" export is defined on the "mock:/src/example.ts"') - expect(() => example.array).toThrowError('[vitest] No "array" export is defined on the "mock:/src/example.ts"') - expect(() => example.someClasses).toThrowError('[vitest] No "someClasses" export is defined on the "mock:/src/example.ts"') + expect(() => example.default).toThrowError('[vitest] No "default" export is defined on the "/src/example.ts"') + expect(() => example.boolean).toThrowError('[vitest] No "boolean" export is defined on the "/src/example.ts"') + expect(() => example.object).toThrowError('[vitest] No "object" export is defined on the "/src/example.ts"') + expect(() => example.array).toThrowError('[vitest] No "array" export is defined on the "/src/example.ts"') + expect(() => example.someClasses).toThrowError('[vitest] No "someClasses" export is defined on the "/src/example.ts"') }) it('non-object return on factory gives error', async () => { diff --git a/packages/vitest/src/runtime/mocker.ts b/packages/vitest/src/runtime/mocker.ts index 33c5c8355739..4ace943b8dc3 100644 --- a/packages/vitest/src/runtime/mocker.ts +++ b/packages/vitest/src/runtime/mocker.ts @@ -106,13 +106,26 @@ export class VitestMocker { const cached = this.moduleCache.get(dep)?.exports if (cached) return cached - const exports = await mock() + let exports: any + try { + exports = await mock() + } + catch (err) { + const vitestError = new Error( + '[vitest] There was an error, when mocking a module. ' + + 'If you are using vi.mock, make sure you are not using top level variables inside, since this call is hoisted. ' + + 'Read more: https://vitest.dev/api/#vi-mock') + vitestError.cause = err + throw vitestError + } if (exports === null || typeof exports !== 'object') throw new Error('[vitest] vi.mock(path: string, factory?: () => unknown) is not returning an object. Did you mean to return an object with a "default" key?') this.moduleCache.set(dep, { exports }) + const filepath = dep.slice('mock:'.length) + const exportHandler = { get(target: Record, prop: any) { const val = target[prop] @@ -123,7 +136,7 @@ export class VitestMocker { return target.then.bind(target) } else if (!(prop in target)) { - throw new Error(`[vitest] No "${prop}" export is defined on the "${dep}"`) + throw new Error(`[vitest] No "${prop}" export is defined on the "${filepath}"`) } return val