testing
Skill452 starsChanged 23 days agoApache-2.0View on GitHub
Use when writing React hook tests, encountering test failures, flaky tests, cross-file contamination, or "Hook timed out" errors - Bun test patterns with proper spy cleanup
We split this skill into the rules it gives an agent
17 rules. 0 are common, 5 are distinctive, and 12 are very specific to this project.
0
Common
Most of the 171 edited versions we read keep these.
5
Distinctive
This author's own choices. Worth a look.
12
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 134
Solution: Use spyOn() instead of mock.module().
- Very specific: line 180
Import module as namespace: import * as apiModule from './api'
- Very specific: line 181
Create spies in beforeEach: spyOn(apiModule, 'function')
- Very specific: line 182
Always mockRestore() in afterEach
- Very specific: line 183
Use mock variables in assertions: expect(mockFn) not expect(apiModule.fn)
- Very specific: line 187
Use mock.module() only for infrastructure every test needs, configured in a preload, or for a module that will never be imported as real …
- Distinctive: line 193
Do not solve one contaminated test by lowering global concurrency, changing CI, or adding runtime dependency-indirection files solely for …
- Very specific: line 210
Use mockClear() when only call history should reset.
- Distinctive: line 214
If a subject reads an environment flag at module scope, changing the mock after the static import cannot affect it.
- Distinctive: line 222
If a test passes alone and fails in the suite, run likely contaminators before the victim in one command:
- Very specific: line 344
Search for mock.module() calls
- Very specific: line 353
Verify spy setup: spyOn(module, 'correctFunctionName')
- Very specific: line 354
Check assertions use mock variable: expect(mockFn) not expect(module.fn)
- Very specific: line 362
Ensure all async operations are awaited
- Very specific: line 363
Check mock returns resolved promises: mockResolvedValue()
- Distinctive: line 393
Red: Write failing test for hook behavior
- Distinctive: line 396
Repeat: Add next test case
“Common” is measured: kept by at least 6 in 10 of the 171 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Bun Testing Patterns
Overview
Bun's test runner provides Jest-compatible API with TypeScript support and fast execution. Critical: Test globals (describe, it, expect, mock, spyOn, beforeEach, afterEach) are available globally via tooling/global.d.ts - no imports needed. mock.module() is process-global - use spyOn() instead.
Setup (First-Time Installation)
1. bunfig.toml
[test]
# Preload scripts execute BEFORE any test file
preload = ["./tooling/test-setup.ts"]
# Coverage exclusions
coveragePathIgnorePatterns = [
"node_modules/**",
"**/*.d.ts",
]2. tooling/global.d.ts
/// <reference types="bun-types/test-globals" />
declare var mock: typeof import("bun:test").mock;
declare var spyOn: typeof import("bun:test").spyOn;3. tooling/test-setup.ts
import { afterEach, expect, mock, spyOn } from "bun:test";
import { GlobalRegistrator } from "@happy-dom/global-registrator";
import * as matchers from "@testing-library/jest-dom/matchers";
import { cleanup } from "@testing-library/react";
// Make mock and spyOn globally available
(globalThis as any).mock = mock;
(globalThis as any).spyOn = spyOn;
// Register DOM globals synchronously
GlobalRegistrator.register();
// Ensure document.body exists
if (global.document && !global.document.body) {
const body = global.document.createElement("body");
global.document.documentElement.appendChild(body);
}
// Extend Bun's expect with Testing Library matchers
expect.extend(matchers);
// Cleanup after each test
afterEach(() => {
cleanup();
});4. Dependencies
bun add -d @happy-dom/global-registrator @testing-library/react @testing-library/jest-domWhen to Use
- Writing new tests for React hooks
- Debugging test failures, especially when tests pass individually but fail in full suite
- Fixing cross-file contamination ("test passes alone, fails with others")
- Encountering "Hook timed out", race conditions, or flaky tests
Quick Reference
| Pattern | Use Case | Example |
| No imports needed | Test globals | describe, it, expect, mock, spyOn are global |
toMatchObject(array) | Array partial match | Checks properties exist, allows extras |
toEqual() | Exact match | Validates complete structure |
expect(val as any) | Type mismatch | Cast actual value, not expected |
mock() not jest.fn() | Create mock function | Bun test API |
spyOn() + afterEach | Mock with cleanup | Always spy.mockRestore() |
renderHook() + act() | Test hooks | Wrap state changes in act() |
void act() | Prevent warnings | Use with sync click/change events |
ReturnType<typeof mock> | Type mock variables | let mockFn: ReturnType<typeof mock> |
ReturnType<typeof spyOn> | Type spy variables | let spy: ReturnType<typeof spyOn> |
Core Patterns
Test File Structure
// NO bun:test imports needed - globals are available
import { act, renderHook } from "@testing-library/react";
import * as apiModule from "@/lib/api"; // Import as namespace for spyOn
describe("HookName", () => {
let mockFunction: ReturnType<typeof mock>;
let functionSpy: ReturnType<typeof spyOn>;
beforeEach(() => {
mockFunction = mock();
functionSpy = spyOn(apiModule, "functionName").mockImplementation(
mockFunction
);
mockFunction.mockResolvedValue(defaultResponse);
});
afterEach(() => {
// CRITICAL: Always restore spies
functionSpy.mockRestore();
});
it("should do something", async () => {
// Test implementation
});
});Avoiding Cross-File Contamination
Problem: mock.module() is process-global. If fileA.test.ts uses mock.module('@/lib/api'), it contaminates fileB.test.ts.
Solution: Use spyOn() instead of mock.module().
❌ WRONG - Causes Cross-Contamination
// This globally mocks the module for ALL test files
mock.module("@/lib/api", () => ({
fetchData: mock(),
}));✅ CORRECT - File-Scoped Mocking
import * as apiModule from "@/lib/api";
describe("MyHook", () => {
let mockFetchData: ReturnType<typeof mock>;
let fetchDataSpy: ReturnType<typeof spyOn>;
beforeEach(() => {
mockFetchData = mock();
fetchDataSpy = spyOn(apiModule, "fetchData").mockImplementation(
mockFetchData
);
mockFetchData.mockResolvedValue({ data: "test" });
});
afterEach(() => {
fetchDataSpy.mockRestore();
});
it("fetches data", async () => {
const { result } = renderHook(() => useMyHook());
await act(async () => {
await result.current.fetch();
});
expect(mockFetchData).toHaveBeenCalled();
});
});Key differences:
- Import module as namespace:
import * as apiModule from './api' - Create spies in
beforeEach:spyOn(apiModule, 'function') - Always
mockRestore()inafterEach - Use mock variables in assertions:
expect(mockFn)notexpect(apiModule.fn)
When mock.module() Is Allowed
Use mock.module() only for infrastructure every test needs, configured in a
preload, or for a module that will never be imported as real code elsewhere in
the same Bun process. It mutates process-global module state and may preserve
unmocked real exports while overriding only returned keys. The result can look
real while a few exports are poisoned.
Do not solve one contaminated test by lowering global concurrency, changing CI, or adding runtime dependency-indirection files solely for tests. Prefer a lower-level contract, a file-scoped spy, or a different integration harness.
mockReset() Destroys Implementations
mockReset() clears calls and the implementation. Restore defaults immediately:
const fetchValue = mock(async () => "default");
beforeEach(() => {
fetchValue.mockReset();
fetchValue.mockResolvedValue("default");
});Use mockClear() when only call history should reset.
Module-Scope Environment Values Need Fresh Imports
If a subject reads an environment flag at module scope, changing the mock after the static import cannot affect it. Set a mutable environment mock first, then dynamically import the subject. Add a deterministic cache-busting import query only when Bun otherwise reuses the module. Avoid random cache keys in committed tests when an incrementing case id works.
Reproducing Order-Dependent Failures
If a test passes alone and fails in the suite, run likely contaminators before the victim in one command:
bun test likely-contaminator.test.ts victim.test.tsRecord the minimal ordering that fails. Inspect module keys, critical function types, and resolved default values behind a temporary opt-in debug flag. Remove debug output after the owner is proven.
Testing React Hooks
import { act, renderHook } from "@testing-library/react";
it("updates state correctly", async () => {
const { result } = renderHook(() => useCustomHook());
await act(async () => {
await result.current.fetchData();
});
expect(result.current.data).toEqual(expectedData);
expect(result.current.loading).toBe(false);
});
// Use void to prevent unused promise warnings
void act(() => getByText("button").click());Custom wrapper pattern for context providers:
const createWrapper = (props) => ({ children }: any) => (
<Provider {...props}>{children}</Provider>
);
const wrapper = createWrapper({ value: 'test' });
const { result } = renderHook(() => useCustomHook(), { wrapper });Testing Async Errors
it("handles async errors", async () => {
mockFetch.mockRejectedValue(new Error("Network error"));
const { result } = renderHook(() => useCustomHook());
await act(async () => {
try {
await result.current.fetchData();
} catch (error) {
expect(error).toEqual(new Error("Failed to load"));
}
});
expect(result.current.error).toBe("Failed to load");
});Matcher Selection
// ✅ Array partial matching
expect(children).toMatchObject([{ text: "one" }, { text: "two" }]);
// ✅ Exact matching
expect(result).toEqual({ data: "test" });
// ✅ Cast actual value for type mismatches
expect(node as any).toEqual({ text: "one" });
// ❌ Don't cast expected value
expect(children).toEqual([{ text: "one" }] as any);
// ❌ Don't use toMatchObject() for single objects (won't catch extra props)
expect(node).toMatchObject({ text: "one" });Running Tests
# Run all tests
bun test
# Run specific file
bun test src/hooks/useMyHook.test.ts
# Watch mode
bun test --watch
# Coverage
bun test --coverage
# Bail on first failure
bun test --bailCommon Mistakes
| Mistake | Problem | Fix |
Importing from bun:test | Unnecessary, globals available | Remove imports |
Using mock.module() | Cross-file contamination | Use spyOn() + afterEach cleanup |
Forgetting afterEach cleanup | Spies persist across tests | Always spy.mockRestore() |
| Direct import for spyOn | Can't spy on named exports | import * as module |
Forgetting act() | React warnings, flaky tests | Wrap state changes in act() |
jest.fn() / jest.Mock | Wrong framework | Use mock() and ReturnType<typeof mock> |
| No type for mocks | Type errors, autocomplete fails | ReturnType<typeof mock> |
Debugging Test Failures
Test passes alone, fails in suite
Symptom: bun test file.test.ts passes, bun test fails.
Cause: Cross-file contamination from mock.module().
Fix:
- Search for
mock.module()calls - Refactor to
spyOn()pattern withafterEachcleanup
"Expected to be called but it was not called"
Cause: Wrong mock variable or wrong function name.
Fix:
- Verify spy setup:
spyOn(module, 'correctFunctionName') - Check assertions use mock variable:
expect(mockFn)notexpect(module.fn)
"Hook timed out after 5000ms"
Cause: Missing await, unresolved promise.
Fix:
- Ensure all async operations are
awaited - Check mock returns resolved promises:
mockResolvedValue() - Increase timeout if needed:
it('name', fn, 10000)
Red Flags - Cross-Contamination Risk
- Using
mock.module()outside of preload scripts - Importing modules directly instead of as namespace for spyOn
- Missing
afterEach()withmockRestore()calls - Tests passing individually but failing in full suite
All indicate cross-file contamination. Refactor to spyOn pattern.
Implementation Checklist
For each new hook test file:
- NO imports from
bun:test- globals are available - Import
renderHook,actfrom@testing-library/reactwhen testing hooks - Import modules as namespace for spyOn:
import * as module - Declare mock variables with
ReturnType<typeof mock> - Declare spy variables with
ReturnType<typeof spyOn> - Create spies in
beforeEachwithspyOn(module, 'fn') - Set default mock return values in
beforeEach - CRITICAL: Restore spies in
afterEachwithspy.mockRestore() - Wrap React state changes in
act() - Use mock variables in assertions, not module functions
- Verify tests pass both individually and in full suite
TDD Workflow
- Red: Write failing test for hook behavior
- Green: Implement minimal code to pass test
- Refactor: Clean up implementation
- Repeat: Add next test case
Other versions191 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 191
- modelcontextprotocol/servers★ 92ksame name
- lobehub/lobe-chat★ 84ksame name
- lobehub/lobehub★ 84ksame name
- penpot/penpot★ 62ksame name
- meteor/meteor★ 45ksame name
- iOfficeAI/AionUi★ 34ksame name
- garrytan/gbrain★ 31ksame name
- OpenZeppelin/openzeppelin-contracts★ 27ksame name
- tursodatabase/turso★ 25ksame name
- redis/go-redis★ 22ksame name
181 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
Used alongsideProjects with it also use bundle-dependency-prs, update-library-template and 8 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
MicrosoftSkillbundle-dependency-prs
Fix broken dependency update PRs and aggregate the ones that work into one PR.
Also in 6 projects that use this
MicrosoftSkillupdate-library-template
Merges the latest Library.Template into this repo (at position of HEAD) and resolves conflicts.
Also in 6 projects that use this
zetaloopSkillassign-copilot
Assigns a GitHub issue to the Copilot coding agent, optionally specifying a custom agent. Use this when asked to assign an issue to Copilot or delegate an issue to CCA.
Also in 3 projects that use this
timmo001Skillchangeset-scope
Keep all scoped code work contained to the user-defined changeset. Use for implementation, fixes, diagnosis, refactoring, cleanup, and review when explicit instructions, named files, diffs, branches, pull requests, or injected work scopes define the boundary.
Also in 3 projects that use this
JetBrainsSkillmodule-dependencies
Add or modify IntelliJ module dependencies in `.iml` files.
Also in 3 projects that use this
CES-LtdSkilloss-pr
Use when creating a pull request, after committing changes, or when user invokes /oss-pr. Covers branch management, quality checks, commit, push, and PR creation.
Also in 3 projects that use this
In this repository54 other files it gives its agents.
- .agents/AGENTS.mdAGENTS.md
- AGENTS.mdAGENTS.md
- .agents/skills/autoreview/AGENTS.mdAGENTS.md
- .agents/skills/autoreview/CLAUDE.mdCLAUDE.md
- CLAUDE.mdCLAUDE.md
- 4-ultraciteSkill
- agent-browser-issueSkill
- agent-native-reviewerSkill
- architecture-cleanupSkill
- autoclosureSkill
- autogoalSkill
- autoreviewSkill
- autoSkill
- avoid-feature-creepSkill
- better-auth-best-practicesSkill
- changeset-doc-syncSkill
- changeset-resolveSkill
- changesetSkill
- component-colocationSkill
- concave-paritySkill
- concaveSkill
- convex-release-auditSkill
- designSkill
- deslopSkill
- diagnosing-bugsSkill
- find-skillsSkill
- grill-with-visionSkill
- hard-cutSkill
- jotai-xSkill
- kitcnSkill
- linear-backlogSkill
- major-taskSkill
- next-dev-loopSkill
- nextjsSkill
- orchestratorSkill
- package-release-auditSkill
- prototypeSkill
- react-querySkill
- research-wikiSkill
- resolve-pr-feedbackSkill
- resolving-merge-conflictsSkill
- scenariosSkill
- shadcn-paritySkill
- sync-convex-authSkill
- sync-visionSkill
- taskSkill
- tddSkill
- toastSkill
- to-milestoneSkill
- to-prdSkill
- turborepoSkill
- video-transcriptsSkill
- visionSkill
- walkthroughSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

