diff --git a/docs/test/mocks.md b/docs/test/mocks.md index 645e2a3945..724e2cef3b 100644 --- a/docs/test/mocks.md +++ b/docs/test/mocks.md @@ -93,3 +93,106 @@ test("spyon", () => { expect(spy).toHaveBeenCalledTimes(1); }); ``` + +## Module mocks with `mock.module()` + +Module mocking lets you override the behavior of a module. Use `mock.module(path: string, callback: () => Object)` to mock a module. + +```ts +import { test, expect, mock } from "bun:test"; + +mock.module("./module", () => { + return { + foo: "bar", + }; +}); + +test("mock.module", async () => { + const esm = await import("./module"); + expect(esm.foo).toBe("bar"); + + const cjs = require("./module"); + expect(cjs.foo).toBe("bar"); +}); +``` + +Like the rest of Bun, module mocks support both `import` and `require`. + +### Overriding already imported modules + +If you need to override a module that's already been imported, there's nothing special you need to do. Just call `mock.module()` and the module will be overridden. + +```ts +import { test, expect, mock } from "bun:test"; + +// The module we're going to mock is here: +import { foo } from "./module"; + +test("mock.module", async () => { + const cjs = require("./module"); + expect(foo).toBe("bar"); + expect(cjs.foo).toBe("bar"); + + // We update it here: + mock.module("./module", () => { + return { + foo: "baz", + }; + }); + + // And the live bindings are updated. + expect(foo).toBe("baz"); + + // The module is also updated for CJS. + expect(cjs.foo).toBe("baz"); +}); +``` + +### Hoisting & preloading + +If you need to ensure a module is mocked before it's imported, you should use `--preload` to load your mocks before your tests run. + +```ts +// my-preload.ts +import { mock } from "bun:test"; + +mock.module("./module", () => { + return { + foo: "bar", + }; +}); +``` + +```sh +bun test --preload ./my-preload +``` + +To make your life easier, you can put `preload` in your `bunfig.toml`: + +```toml + +[test] +# Load these modules before running tests. +preload = ["./my-preload"] + +``` + +#### What happens if I mock a module that's already been imported? + +If you mock a module that's already been imported, the module will be updated in the module cache. This means that any modules that import the module will get the mocked version, BUT the original module will still have been evaluated. That means that any side effects from the original module will still have happened. + +If you want to prevent the original module from being evaluated, you should use `--preload` to load your mocks before your tests run. + +### `__mocks__` directory and auto-mocking + +Auto-mocking is not supported yet. If this is blocking you from switching to Bun, please file an issue. + +### Implementation details + +Module mocks have different implementations for ESM and CommonJS modules. For ES Modules, we've added patches to JavaScriptCore that allow Bun to override export values at runtime and update live bindings recursively. + +As of Bun v1.0.19, Bun automatically resolves the `specifier` argument to `mock.module()` as though you did an `import`. If it successfully resolves, then the resolved specifier string is used as the key in the module cache. This means that you can use relative paths, absolute paths, and even module names. If the `specifier` doesn't resolve, then the original `specifier` is used as the key in the module cache. + +After resolution, the mocked module is stored in the ES Module registry **and** the CommonJS require cache. This means that you can use `import` and `require` interchangeably for mocked modules. + +The callback function is called lazily, only if the module is imported or required. This means that you can use `mock.module()` to mock modules that don't exist yet, and it means that you can use `mock.module()` to mock modules that are imported by other modules. diff --git a/src/bun.js/bindings/BunPlugin.cpp b/src/bun.js/bindings/BunPlugin.cpp index 48267619ce..7c89c5dd2d 100644 --- a/src/bun.js/bindings/BunPlugin.cpp +++ b/src/bun.js/bindings/BunPlugin.cpp @@ -516,6 +516,7 @@ JSObject* JSModuleMock::executeOnce(JSC::JSGlobalObject* lexicalGlobalObject) return object; } +extern "C" JSC::EncodedJSValue Bun__resolveSyncWithSource(JSC::JSGlobalObject* global, JSC::EncodedJSValue specifier, BunString* from, bool is_esm); extern "C" EncodedJSValue JSMock__jsModuleMock(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callframe) { JSC::VM& vm = lexicalGlobalObject->vm(); @@ -541,18 +542,58 @@ extern "C" EncodedJSValue JSMock__jsModuleMock(JSC::JSGlobalObject* lexicalGloba return {}; } - if (specifier.startsWith("./"_s) || specifier.startsWith("../"_s) || specifier == "."_s) { + auto resolveSpecifier = [&]() -> void { JSC::SourceOrigin sourceOrigin = callframe->callerSourceOrigin(vm); const URL& url = sourceOrigin.url(); - if (url.protocolIsFile()) { - URL joinedURL = URL(url, specifier); - specifier = joinedURL.fileSystemPath(); - specifierString = jsString(vm, specifier); - } else { - scope.throwException(lexicalGlobalObject, JSC::createTypeError(lexicalGlobalObject, "mock(module, fn) cannot mock relative paths in non-files"_s)); - return {}; + + if (specifier.startsWith("file:"_s)) { + URL fileURL = URL(url, specifier); + if (fileURL.isValid()) { + specifier = fileURL.fileSystemPath(); + specifierString = jsString(vm, specifier); + globalObject->onLoadPlugins.mustDoExpensiveRelativeLookup = true; + return; + } else { + scope.throwException(lexicalGlobalObject, JSC::createTypeError(lexicalGlobalObject, "Invalid \"file:\" URL"_s)); + return; + } } - } + + if (url.isValid() && url.protocolIsFile()) { + auto fromString = url.fileSystemPath(); + BunString from = Bun::toString(fromString); + auto catchScope = DECLARE_CATCH_SCOPE(vm); + auto result = JSValue::decode(Bun__resolveSyncWithSource(globalObject, JSValue::encode(specifierString), &from, true)); + if (catchScope.exception()) { + catchScope.clearException(); + } + + if (result && result.isString()) { + auto* specifierStr = result.toString(globalObject); + if (specifierStr->length() > 0) { + specifierString = specifierStr; + specifier = specifierString->value(globalObject); + } + } else if (specifier.startsWith("./"_s) || specifier.startsWith(".."_s)) { + // If module resolution fails, we try to resolve it relative to the current file + auto relativeURL = URL(url, specifier); + + if (relativeURL.isValid()) { + globalObject->onLoadPlugins.mustDoExpensiveRelativeLookup = true; + + if (relativeURL.protocolIsFile()) + specifier = relativeURL.fileSystemPath(); + else + specifier = relativeURL.string(); + + specifierString = jsString(vm, specifier); + } + } + } + }; + + resolveSpecifier(); + RETURN_IF_EXCEPTION(scope, {}); JSC::JSValue callbackValue = callframe->argument(1); if (!callbackValue.isCell() || !callbackValue.isCallable()) { @@ -738,6 +779,25 @@ EncodedJSValue BunPlugin::OnLoad::run(JSC::JSGlobalObject* globalObject, BunStri RELEASE_AND_RETURN(throwScope, JSValue::encode(result)); } +std::optional BunPlugin::OnLoad::resolveVirtualModule(const String& path, const String& from) +{ + ASSERT(virtualModules); + + if (this->mustDoExpensiveRelativeLookup) { + String joinedPath = path; + + if (path.startsWith("./"_s) || path.startsWith(".."_s)) { + auto url = WTF::URL::fileURLWithFileSystemPath(from); + ASSERT(url.isValid()); + joinedPath = URL(url, path).fileSystemPath(); + } + + return virtualModules->contains(joinedPath) ? std::optional { joinedPath } : std::nullopt; + } + + return virtualModules->contains(path) ? std::optional { path } : std::nullopt; +} + EncodedJSValue BunPlugin::OnResolve::run(JSC::JSGlobalObject* globalObject, BunString* namespaceString, BunString* path, BunString* importer) { Group* groupPtr = this->group(namespaceString ? namespaceString->toWTFString(BunString::ZeroCopy) : String()); @@ -848,7 +908,7 @@ JSC::JSValue runVirtualModule(Zig::GlobalObject* globalObject, BunString* specif return JSValue::decode(Bun__runVirtualModule(globalObject, specifier)); }; - if (!globalObject->onLoadPlugins.virtualModules) { + if (!globalObject->onLoadPlugins.hasVirtualModules()) { return fallback(); } auto& virtualModules = *globalObject->onLoadPlugins.virtualModules; @@ -900,4 +960,4 @@ JSC::JSValue runVirtualModule(Zig::GlobalObject* globalObject, BunString* specif return fallback(); } -} \ No newline at end of file +} // namespace Bun \ No newline at end of file diff --git a/src/bun.js/bindings/BunPlugin.h b/src/bun.js/bindings/BunPlugin.h index 4431e32cc2..41f1956cde 100644 --- a/src/bun.js/bindings/BunPlugin.h +++ b/src/bun.js/bindings/BunPlugin.h @@ -70,10 +70,15 @@ public: } VirtualModuleMap* virtualModules = nullptr; + bool mustDoExpensiveRelativeLookup = false; JSC::EncodedJSValue run(JSC::JSGlobalObject* globalObject, BunString* namespaceString, BunString* path); + bool hasVirtualModules() const { return virtualModules != nullptr; } + void addModuleMock(JSC::VM& vm, const String& path, JSC::JSObject* mock); + std::optional resolveVirtualModule(const String& path, const String& from); + ~OnLoad() { if (virtualModules) { diff --git a/src/bun.js/bindings/ImportMetaObject.cpp b/src/bun.js/bindings/ImportMetaObject.cpp index 392347e550..f4fb0273aa 100644 --- a/src/bun.js/bindings/ImportMetaObject.cpp +++ b/src/bun.js/bindings/ImportMetaObject.cpp @@ -42,6 +42,7 @@ #include #include #include "CommonJSModuleRecord.h" +#include #if OS(WINDOWS) #define PLATFORM_SEP_s "\\"_s @@ -105,9 +106,13 @@ static JSC::EncodedJSValue functionRequireResolve(JSC::JSGlobalObject* globalObj JSC::JSValue moduleName = callFrame->argument(0); auto doIt = [&](const WTF::String& fromStr) -> JSC::EncodedJSValue { - if (auto* virtualModules = jsCast(globalObject)->onLoadPlugins.virtualModules) { - if (virtualModules->contains(fromStr)) { - return JSC::JSValue::encode(jsString(vm, fromStr)); + Zig::GlobalObject* zigGlobalObject = jsCast(globalObject); + if (zigGlobalObject->onLoadPlugins.hasVirtualModules()) { + if (auto result = zigGlobalObject->onLoadPlugins.resolveVirtualModule(fromStr, String())) { + if (fromStr == result.value()) + return JSC::JSValue::encode(moduleName); + + return JSC::JSValue::encode(jsString(vm, result.value())); } } @@ -189,8 +194,9 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionRequireResolve, (JSC::JSGlobalObject * global return functionRequireResolve(globalObject, callFrame, fromStr); } -extern "C" JSC::EncodedJSValue functionImportMeta__resolveSync(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) +extern "C" JSC::EncodedJSValue functionImportMeta__resolveSync(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) { + auto* globalObject = jsCast(lexicalGlobalObject); JSC::VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); @@ -207,14 +213,6 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSync(JSC::JSGlobalObje JSC__JSValue from; bool isESM = true; - if (auto* virtualModules = jsCast(globalObject)->onLoadPlugins.virtualModules) { - if (moduleName.isString()) { - if (virtualModules->contains(moduleName.toWTFString(globalObject))) { - return JSC::JSValue::encode(moduleName); - } - } - } - if (callFrame->argumentCount() > 1) { if (callFrame->argumentCount() > 2) { @@ -266,6 +264,17 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSync(JSC::JSGlobalObje from = JSC::JSValue::encode(pathProperty); } + if (globalObject->onLoadPlugins.hasVirtualModules()) { + if (moduleName.isString()) { + auto moduleString = moduleName.toWTFString(globalObject); + if (auto resolvedString = globalObject->onLoadPlugins.resolveVirtualModule(moduleString, JSValue::decode(from).toWTFString(globalObject))) { + if (moduleString == resolvedString.value()) + return JSC::JSValue::encode(moduleName); + return JSC::JSValue::encode(jsString(vm, resolvedString.value())); + } + } + } + auto result = Bun__resolveSync(globalObject, JSC::JSValue::encode(moduleName), from, isESM); if (!JSC::JSValue::decode(result).isString()) { @@ -277,49 +286,52 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSync(JSC::JSGlobalObje return result; } -extern "C" JSC::EncodedJSValue functionImportMeta__resolveSyncPrivate(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) +extern "C" JSC::EncodedJSValue functionImportMeta__resolveSyncPrivate(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame) { - JSC::VM& vm = globalObject->vm(); - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); - auto* global = jsDynamicCast(globalObject); + JSC::VM& vm = lexicalGlobalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + auto* globalObject = jsDynamicCast(lexicalGlobalObject); JSC::JSValue moduleName = callFrame->argument(0); JSValue from = callFrame->argument(1); bool isESM = callFrame->argument(2).asBoolean(); if (moduleName.isUndefinedOrNull()) { - JSC::throwTypeError(globalObject, scope, "expected module name as a string"_s); + JSC::throwTypeError(lexicalGlobalObject, scope, "expected module name as a string"_s); scope.release(); return JSC::JSValue::encode(JSC::JSValue {}); } RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::JSValue {})); - if (auto* virtualModules = global->onLoadPlugins.virtualModules) { + if (globalObject->onLoadPlugins.hasVirtualModules()) { if (moduleName.isString()) { - if (virtualModules->contains(moduleName.toWTFString(globalObject))) { - return JSC::JSValue::encode(moduleName); + auto moduleString = moduleName.toWTFString(globalObject); + if (auto resolvedString = globalObject->onLoadPlugins.resolveVirtualModule(moduleString, from.toWTFString(globalObject))) { + if (moduleString == resolvedString.value()) + return JSC::JSValue::encode(moduleName); + return JSC::JSValue::encode(jsString(vm, resolvedString.value())); } } } if (!isESM) { - if (LIKELY(global)) { - auto overrideHandler = global->m_nodeModuleOverriddenResolveFilename.get(); + if (LIKELY(globalObject)) { + auto overrideHandler = globalObject->m_nodeModuleOverriddenResolveFilename.get(); if (UNLIKELY(overrideHandler)) { ASSERT(overrideHandler->isCallable()); MarkedArgumentBuffer args; args.append(moduleName); args.append(from); - return JSValue::encode(JSC::call(globalObject, overrideHandler, JSC::getCallData(overrideHandler), JSC::jsUndefined(), args)); + return JSValue::encode(JSC::call(lexicalGlobalObject, overrideHandler, JSC::getCallData(overrideHandler), JSC::jsUndefined(), args)); } } } - auto result = Bun__resolveSync(globalObject, JSC::JSValue::encode(moduleName), JSValue::encode(from), isESM); + auto result = Bun__resolveSync(lexicalGlobalObject, JSC::JSValue::encode(moduleName), JSValue::encode(from), isESM); if (!JSC::JSValue::decode(result).isString()) { - JSC::throwException(globalObject, scope, JSC::JSValue::decode(result)); + JSC::throwException(lexicalGlobalObject, scope, JSC::JSValue::decode(result)); return JSC::JSValue::encode(JSC::JSValue {}); } @@ -328,13 +340,14 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSyncPrivate(JSC::JSGlo } JSC_DEFINE_HOST_FUNCTION(functionImportMeta__resolve, - (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) + (JSC::JSGlobalObject * lexicalGlobalObject, JSC::CallFrame* callFrame)) { + auto* globalObject = jsCast(lexicalGlobalObject); JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); switch (callFrame->argumentCount()) { case 0: { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); // not "requires" because "require" could be confusing JSC::throwTypeError(globalObject, scope, "import.meta.resolve needs 1 argument (a string)"_s); scope.release(); @@ -352,14 +365,6 @@ JSC_DEFINE_HOST_FUNCTION(functionImportMeta__resolve, JSC__JSValue from; - if (auto* virtualModules = jsCast(globalObject)->onLoadPlugins.virtualModules) { - if (moduleName.isString()) { - if (virtualModules->contains(moduleName.toWTFString(globalObject))) { - return JSC::JSValue::encode(moduleName); - } - } - } - if (callFrame->argumentCount() > 1 && callFrame->argument(1).isString()) { from = JSC::JSValue::encode(callFrame->argument(1)); } else { @@ -373,6 +378,18 @@ JSC_DEFINE_HOST_FUNCTION(functionImportMeta__resolve, auto clientData = WebCore::clientData(vm); from = JSC::JSValue::encode(thisObject->getIfPropertyExists(globalObject, clientData->builtinNames().pathPublicName())); + RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::JSValue {})); + } + + if (globalObject->onLoadPlugins.hasVirtualModules()) { + if (moduleName.isString()) { + auto moduleString = moduleName.toWTFString(globalObject); + if (auto resolvedString = globalObject->onLoadPlugins.resolveVirtualModule(moduleString, JSValue::decode(from).toWTFString(globalObject))) { + if (moduleString == resolvedString.value()) + return JSC::JSValue::encode(JSPromise::resolvedPromise(globalObject, moduleName)); + return JSC::JSValue::encode(JSPromise::resolvedPromise(globalObject, jsString(vm, resolvedString.value()))); + } + } } return Bun__resolve(globalObject, JSC::JSValue::encode(moduleName), from, true); diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 5b8a5fae07..cdb656cd0e 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -4127,15 +4127,6 @@ JSC::Identifier GlobalObject::moduleLoaderResolve(JSGlobalObject* jsGlobalObject ErrorableString res; res.success = false; - if (key.isString()) { - if (auto* virtualModules = globalObject->onLoadPlugins.virtualModules) { - auto keyString = key.toWTFString(globalObject); - if (virtualModules->contains(keyString)) { - return JSC::Identifier::fromString(globalObject->vm(), keyString); - } - } - } - BunString keyZ; if (key.isString()) { auto moduleName = jsCast(key)->value(globalObject); @@ -4149,10 +4140,20 @@ JSC::Identifier GlobalObject::moduleLoaderResolve(JSGlobalObject* jsGlobalObject } else { keyZ = Bun::toStringRef(moduleName); } + } else { keyZ = Bun::toStringRef(globalObject, key); } BunString referrerZ = referrer && !referrer.isUndefinedOrNull() && referrer.isString() ? Bun::toStringRef(globalObject, referrer) : BunStringEmpty; + + if (globalObject->onLoadPlugins.hasVirtualModules()) { + if (auto resolvedString = globalObject->onLoadPlugins.resolveVirtualModule(keyZ.toWTFString(), referrerZ.toWTFString())) { + return Identifier::fromString(globalObject->vm(), resolvedString.value()); + } + } else { + ASSERT(!globalObject->onLoadPlugins.mustDoExpensiveRelativeLookup); + } + ZigString queryString = { 0, 0 }; Zig__GlobalObject__resolve(&res, globalObject, &keyZ, &referrerZ, &queryString); keyZ.deref(); @@ -4184,10 +4185,10 @@ JSC::JSInternalPromise* GlobalObject::moduleLoaderImportModule(JSGlobalObject* j auto* promise = JSC::JSInternalPromise::create(vm, globalObject->internalPromiseStructure()); RETURN_IF_EXCEPTION(scope, promise->rejectWithCaughtException(globalObject, scope)); - if (auto* virtualModules = globalObject->onLoadPlugins.virtualModules) { + if (globalObject->onLoadPlugins.hasVirtualModules()) { auto keyString = moduleNameValue->value(globalObject); - if (virtualModules->contains(keyString)) { - auto resolvedIdentifier = JSC::Identifier::fromString(vm, keyString); + if (auto resolution = globalObject->onLoadPlugins.resolveVirtualModule(keyString, sourceOrigin.url().protocolIsFile() ? sourceOrigin.url().fileSystemPath() : String())) { + auto resolvedIdentifier = JSC::Identifier::fromString(vm, resolution.value()); auto result = JSC::importModule(globalObject, resolvedIdentifier, JSC::jsUndefined(), parameters, JSC::jsUndefined()); diff --git a/src/js/internal/debugger.ts b/src/js/internal/debugger.ts index 7c8165e6dd..abb1ec3736 100644 --- a/src/js/internal/debugger.ts +++ b/src/js/internal/debugger.ts @@ -25,7 +25,7 @@ export default function ( if (protocol.includes("ws")) { Bun.write(Bun.stderr, `Inspect in browser:\n ${link(`https://debug.bun.sh/#${host}${pathname}`)}\n`); } - Bun.write(Bun.stderr, dim("--------------------- Bun Inspector ---------------------")+ reset() + "\n"); + Bun.write(Bun.stderr, dim("--------------------- Bun Inspector ---------------------") + reset() + "\n"); } const unix = process.env["BUN_INSPECT_NOTIFY"]; diff --git a/test/js/bun/test/mock/mock-module.test.ts b/test/js/bun/test/mock/mock-module.test.ts index 2e516156ca..21c8d2fa5e 100644 --- a/test/js/bun/test/mock/mock-module.test.ts +++ b/test/js/bun/test/mock/mock-module.test.ts @@ -7,7 +7,7 @@ // - Write test for export {foo} from "./foo" // - Write test for import {foo} from "./foo"; export {foo} -import { expect, mock, spyOn, test } from "bun:test"; +import { expect, mock, spyOn, test, describe } from "bun:test"; import { fn, iCallFn, variable, default as defaultValue, rexported, rexportedAs } from "./mock-module-fixture"; import * as spyFixture from "./spymodule-fixture"; @@ -30,13 +30,60 @@ test("spyOn", () => { expect(spyFixture.iSpy).toHaveBeenCalled(); }); +test("mocking a module that points to a file which does not resolve successfully still works", async () => { + mock.module("i-never-existed-and-i-never-will", () => { + return { + bar: 42, + }; + }); + + // @ts-expect-error + const { bar } = await import("i-never-existed-and-i-never-will"); + + expect(bar).toBe(42); +}); + +test("mocking a non-existant relative file with a file URL", async () => { + expect(() => require.resolve("./hey-hey-you-you2.ts")).toThrow(); + mock.module("file:./hey-hey-you-you2.ts", () => { + return { + bar: 42, + }; + }); + + // @ts-expect-error + const { bar } = await import("./hey-hey-you-you2.ts"); + expect(bar).toBe(42); + + expect(require("./hey-hey-you-you2.ts").bar).toBe(42); + expect(require.resolve("./hey-hey-you-you2.ts")).toBe(import.meta.resolveSync("./hey-hey-you-you2.ts")); + expect(require.resolve("./hey-hey-you-you2.ts")).toBe(await import.meta.resolve("./hey-hey-you-you2.ts")); +}); + +test("mocking a non-existant relative file", async () => { + expect(() => require.resolve("./hey-hey-you-you.ts")).toThrow(); + mock.module("./hey-hey-you-you.ts", () => { + return { + bar: 42, + }; + }); + + // @ts-expect-error + const { bar } = await import("./hey-hey-you-you.ts"); + expect(bar).toBe(42); + + expect(require("./hey-hey-you-you.ts").bar).toBe(42); + expect(require.resolve("./hey-hey-you-you.ts")).toBe(import.meta.resolveSync("./hey-hey-you-you.ts")); + expect(require.resolve("./hey-hey-you-you.ts")).toBe(await import.meta.resolve("./hey-hey-you-you.ts")); +}); + test("mocking a local file", async () => { expect(fn()).toEqual(42); expect(variable).toEqual(7); expect(defaultValue).toEqual("original"); expect(rexported).toEqual(42); - mock.module("./mock-module-fixture.ts", () => { + mock.module("./mock-module-fixture", () => { return { fn: () => 1, variable: 8, @@ -46,12 +93,11 @@ test("mocking a local file", async () => { }); expect(fn()).toEqual(1); expect(variable).toEqual(8); - // @ts-expect-error // expect(defaultValue).toEqual(42); expect(rexported).toEqual(43); expect(rexportedAs).toEqual(43); expect((await import("./re-export-fixture")).rexported).toEqual(43); - mock.module("./mock-module-fixture.ts", () => { + mock.module("./mock-module-fixture", () => { return { fn: () => 2, variable: 9, @@ -59,7 +105,7 @@ test("mocking a local file", async () => { }); expect(fn()).toEqual(2); expect(variable).toEqual(9); - mock.module("./mock-module-fixture.ts", () => { + mock.module("./mock-module-fixture", () => { return { fn: () => 3, variable: 10,