Skip to content

Chrome fakes

The fakes are installed by the Vitest preset or by calling installChromeFakes() directly. Each namespace is wrapped in a Proxy that throws on access to unmodeled members.


Import: import { createRuntimeFake } from 'extforge/testing';

Member Type Notes
chrome.runtime.id string Fixed to 'extforge-test-extension-id'
chrome.runtime.onInstalled event .addListener / .removeListener
chrome.runtime.onStartup event .addListener / .removeListener
chrome.runtime.onMessage event .addListener / .removeListener
chrome.runtime.sendMessage spy Resolves undefined by default
chrome.runtime.reload spy No-op

getURL, connect, onConnect, getManifest, openOptionsPage, and all other runtime.* members throw the not-modeled trap error.

Control Description
fakes.runtime.fireOnInstalled(details?) Calls all onInstalled listeners. Default details: { reason: 'install' }.
fakes.runtime.fireOnStartup() Calls all onStartup listeners.
fakes.runtime.fireOnMessage(message, sender?) Calls all onMessage listeners and returns a Promise that resolves to the sendResponse argument. If no listener calls sendResponse, resolves undefined.
fakes.runtime.reset() Clears all listeners and resets spy call counts.
import { fakes } from 'extforge/testing/vitest';
import { expect, it } from 'vitest';
it('fires onInstalled with reason update', () => {
let receivedReason = '';
chrome.runtime.onInstalled.addListener(({ reason }) => {
receivedReason = reason;
});
fakes.runtime.fireOnInstalled({ reason: 'update' });
expect(receivedReason).toBe('update');
});

Import: import { createStorageFake } from 'extforge/testing';

Three storage areas: chrome.storage.local, chrome.storage.sync, and chrome.storage.session. Each area models:

Method Behavior
get(keys?) Returns matching keys from in-memory state. Pass null or omit for all keys.
set(items) Merges items into in-memory state.
remove(keys) Deletes keys from state.
clear() Empties the state object.

All methods are spies with .calls and .reset().

chrome.storage.managed, chrome.storage.onChanged, and any method other than the four above.

Control Description
fakes.storage.chrome.local.__state() Returns a snapshot of the current local storage state. Same for sync and session.
fakes.storage.reset() Clears state and resets call counts for all three areas.
import { fakes } from 'extforge/testing/vitest';
import { expect, it } from 'vitest';
it('persists storage across calls within a test', async () => {
await chrome.storage.local.set({ theme: 'dark' });
await chrome.storage.local.set({ font: 'mono' });
const all = fakes.storage.chrome.local.__state();
expect(all).toEqual({ theme: 'dark', font: 'mono' });
});

Import: import { createTabsFake } from 'extforge/testing';

Method Behavior
chrome.tabs.query(info) Filters the seeded tab list by active and url.
chrome.tabs.sendMessage(tabId, message) Spy; resolves undefined.
chrome.tabs.create(props) Adds a new tab to the internal list; returns the tab record.
chrome.tabs.reload(tabId) Spy; no-op.

get, update, remove, onActivated, onUpdated, onRemoved, and all other tabs.* members.

Control Description
fakes.tabs.__seed(tabs) Replaces the tab list. Each entry: { id, url, active? }. Repeated calls overwrite.
fakes.tabs.reset() Clears the tab list and resets all spy call counts.
import { fakes } from 'extforge/testing/vitest';
import { expect, it, beforeEach } from 'vitest';
beforeEach(() => {
fakes.tabs.__seed([
{ id: 100, url: 'https://example.com/', active: true },
{ id: 101, url: 'https://other.com/', active: false },
]);
});
it('finds the active tab', async () => {
const [tab] = await chrome.tabs.query({ active: true });
expect(tab!.id).toBe(100);
});

Import: import { createActionFake } from 'extforge/testing';

Method Behavior
chrome.action.setBadgeText({ text, tabId? }) Stores text keyed by tabId (or 'global').
chrome.action.getBadgeText({ tabId? }) Returns stored text for that key.
chrome.action.setIcon(details) Spy; no-op.
chrome.action.enable(tabId?) Spy; no-op.
chrome.action.disable(tabId?) Spy; no-op.

setPopup, getPopup, setBadgeBackgroundColor, openPopup, and all other action.* members.

Control Description
fakes.action.reset() Clears badge state and resets spy call counts.
import { fakes } from 'extforge/testing/vitest';
import { expect, it } from 'vitest';
it('sets and reads badge text', async () => {
await chrome.action.setBadgeText({ text: '5' });
const text = await chrome.action.getBadgeText({});
expect(text).toBe('5');
});

Import: import { createScriptingFake } from 'extforge/testing';

Method Behavior
chrome.scripting.executeScript(injection) Returns [{ result: undefined, frameId: 0 }] by default. Use __nextResult to queue a specific return value.

insertCSS, removeCSS, registerContentScripts, getRegisteredContentScripts, and all other scripting.* members.

Control Description
fakes.scripting.__nextResult(value) Queues value as the result field of the next executeScript call. Multiple calls queue up in order.
fakes.scripting.reset() Clears the queue and resets spy call counts.
import { fakes } from 'extforge/testing/vitest';
import { expect, it } from 'vitest';
it('returns the queued executeScript result', async () => {
fakes.scripting.__nextResult(42);
const [frame] = await chrome.scripting.executeScript({
target: { tabId: 1 },
func: () => 42,
});
expect(frame!.result).toBe(42);
});

Accessing any member not listed above throws:

chrome.<ns>.<method> is not modeled by extforge/testing v1;
supply your own mock or extend the fake.
Docs: https://extforge.arshadshah.com/testing#unmodeled

To add a stub for an unmodeled method in a test file:

// Extend globally for the duration of the test file
(globalThis as any).chrome.history = {
search: vi.fn().mockResolvedValue([]),
};

Or, use the per-namespace factory and spread your method before passing the fake to your code:

import { createTabsFake } from 'extforge/testing';
const tabs = createTabsFake();
const extendedChrome = {
...tabs.chrome,
get: vi.fn().mockResolvedValue({ id: 1, url: 'https://example.com' }),
};

extforge/testing exports the installer plus a factory and a type per namespace.

Export Kind Notes
installChromeFakes() function Install a fresh ChromeFakes on globalThis.chrome. Returns the handle.
resetChromeFakes() function Reset state on the currently installed fakes (per-test cleanup).
createChromeFakes() function Build a ChromeFakes handle without touching globals.
createRuntimeFake() · createStorageFake() · createTabsFake() · createActionFake() · createScriptingFake() functions Per-namespace factories (see each section above).
ChromeFakes type The aggregate handle returned by installChromeFakes / createChromeFakes.
RuntimeFake · StorageFake · StorageAreaFake · TabsFake · TabRecord · ActionFake · ScriptingFake · ExecuteScriptInjection types Handles and record shapes for the individual namespace fakes.