Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/test/mocks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,8 @@ As in Vitest, `vi.resetModules()` leaves packages in `node_modules` loaded, so a

Module mocks are removed when the test file ends. The next file gets the real module, and modules that imported the mock are re-evaluated. So is a module that made a mock, so a helper that calls `vi.mock()` mocks for every test file that imports it. Other modules stay cached between files.

A module that called `import()` for the mock counts as one that imported it, whether or not it kept the result. So a helper such as `path => import(path)` is re-evaluated too, and the state that it keeps starts over.

Mocks made in a `--preload` script apply to every test file.

A test file can mock a module that the preload script already mocked. With `vi.mock()` and `vi.doMock()`, only the modules the test file imports get the file's mock. Modules the preload script imported keep what they imported, as in Vitest. With `mock.module()` and `jest.mock()`, the modules the preload script imported get the file's mock too.
Expand Down Expand Up @@ -796,7 +798,7 @@ test("lazy import", async () => {

- A `vi.mock()` call that isn't hoisted, such as one inside a function, updates a module of your own that's already imported in place. Its existing exports change, exports that only the mock has aren't added, and getters are read once. A module that only `vi.importActual()` has loaded stays the original, and the next import calls the factory.
- To update a module in place, Bun calls the factory when you mock the module, not at the next import. If the factory returns a promise, an `import()` of the module doesn't wait for it. Await what `vi.mock()` returns instead. `vi.doMock()` does the same for a builtin module such as `node:fs`.
- After `vi.doUnmock()`, a module that imported the mock is evaluated again at its next import, and gets the real module. After a second `vi.doMock()` of the same module, it gets the new mock. In Vitest it stays cached with the first mock until `vi.resetModules()`.
- After `vi.doUnmock()`, a module that imported the mock is evaluated again at its next import, and gets the real module. After a second `vi.doMock()` of the same module, it gets the new mock. In Vitest it stays cached with the first mock until `vi.resetModules()`. A module that called `import()` for the mock counts as one that imported it, unless it has made a mock itself: that module is evaluated again when the test file ends.
- Without `--isolate`, test files share the modules they load. For `vi.doMock()`, a module that an earlier file loaded counts as not loaded, even if this file imports it too. `vi.importActual()` and `importOriginal` evaluate it again.
- Only test files and `--preload` scripts run their imports one at a time. Any other module's imports are all linked before the first one runs, as are those of a test file that uses `bun:test` and has no hoisted call. If one of them calls `vi.mock()` for a module that another one imports, that module is updated in place.
- If a module that is imported with a static import calls `vi.mock()` with an async factory, a module that is imported after it can run before the factory settles. What that module reads from the mocked module while it loads is the original. What it reads later is the mock.
Expand Down
7 changes: 4 additions & 3 deletions src/jsc/VirtualMachine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,8 @@ unsafe extern "C" {
safe fn Zig__GlobalObject__retireForTestIsolation(global: &JSGlobalObject);
safe fn Bun__TestFile__enterModuleGraph(global: &JSGlobalObject) -> u32;
safe fn Bun__Process__resetArgv(global: &JSGlobalObject);
/// BunPlugin.cpp: `key` is the module key of the test file that is loaded next.
safe fn JSMock__didBeginTestFile(global: &JSGlobalObject, key: &bun_core::String);
safe fn Bun__TestFile__leaveModuleGraph(global: &JSGlobalObject);
safe fn Zig__GlobalObject__retireDerivedRealmsForTestIsolation(global: &JSGlobalObject);
safe fn Zig__GlobalObject__destructOnExit(global: &JSGlobalObject);
Expand Down Expand Up @@ -6215,13 +6217,12 @@ impl VirtualMachine {
}
}

// After the preloads, so the modules they loaded still count as loaded by a preload.
crate::cpp::JSMock__didBeginTestFile(self.global());

// Note: reshaped for borrowck.
let global = self.global;
let main = (self.main()).expect("set to the test file above");
let main_str = bun_core::StringView::from_bytes(main);
// After the preloads, so the modules they loaded still count as loaded by a preload.
JSMock__didBeginTestFile(self.global(), &main_str);
let promise = match jsc::JSModuleLoader::resolve_and_load_and_evaluate_module_ptr(
global, &main_str,
) {
Expand Down
Loading
Loading