| name | manage-react-hook-tests |
| description | Create or update test files for React hooks. Use when user asks to "create hook tests", "test useMyHook", "generate tests for hook", "update hook tests", or mentions needing tests for a React hook. Generates vitest test files with renderHook, Fake context providers, and proper test structure. |
Manage React Hook Tests Skill
This skill helps you create or update test files for React hooks. Tests follow a specific pattern with vitest, renderHook from React Testing Library, and Fake context providers for controlled testing.
When to Use This Skill
Use this skill when you need to:
- Create a new test file for a React hook
- Update an existing test file when the hook changes
- Generate test coverage for hook logic and return values
The skill will generate/update:
- A test file with vitest and React Testing Library imports
- A
renderMyHook helper function using renderHook
- Individual test cases for different hook behaviors and branches
- Proper setup with Fake context providers when needed
Usage
Invoke this skill when the user asks to:
- "Create tests for [useMyHook]"
- "Generate a test file for [useMyHook]"
- "I need tests for [useMyHook]"
- "Update tests for [useMyHook]"
- "The [useMyHook] changed, update its tests"
- "Test [useMyHook]"
Core Principles
Testing Philosophy
- Hook-Driven: Test the hook's behavior, return values, and side effects
- Context Isolation: Use Fake context providers to isolate the hook from external dependencies
- Controlled Testing: Use Fake builders to control all dependencies and return values
- Single Responsibility: Each
it block should test one specific scenario
- Avoid vi.mock for Internal: Use Fakes for internal dependencies; only use
vi.mock for external libraries
Prerequisites
Before creating/updating hook tests:
- Verify the hook exists - The hook you're testing must already be defined
- Check for existing test file - Use Glob to search for existing
.spec.tsx file
- Identify hook dependencies - Determine what contexts or other hooks it uses
- Locate Fake providers - Find available Fake context providers (usually in the same package under
/fakes)
- Check for @testing-library/react - Verify that
@testing-library/react is installed (for renderHook)
Create vs Update Decision
If test file exists: Update mode
- Read the existing test file
- Read the hook definition
- Compare and identify what's missing or outdated
- Update the test to match current implementation
If test file does NOT exist: Create mode
- Read the hook definition
- Identify all dependencies and context requirements
- Generate complete test file from scratch
Test File Location
CRITICAL: Test files MUST be in the __tests__ folder, which is a SIBLING of the /src folder, NOT inside it.
Directory Structure
packages/
my-package/
src/
hooks/
useMyHook.ts
__tests__/ # Sibling to src/, NOT inside src/
useMyHook.spec.tsx # .tsx extension for React components
Naming Convention
For a hook named useMyHook:
- Test file:
useMyHook.spec.tsx (matches hook name exactly, with .tsx extension)
- Located in:
packages/my-package/__tests__/useMyHook.spec.tsx
Test File Structure
1. Imports
Import Rules:
- Import React (required for this project's JSX transform)
- Import vitest utilities from
'vitest'
- Import
renderHook (and act if needed) from '@testing-library/react'
- Import the hook being tested
- Import Fake context providers from
/fakes subpath
- Import Fake builders for services/dependencies when needed
Example:
import React from 'react';
import { describe, it, expect, vi } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { useMyHook } from '../src/hooks/useMyHook';
import { FakeMyContextProvider } from '../fakes';
import { FakeServiceBuilder } from '@someScope/some-library/fakes';
import type { IService } from '@someScope/some-library';
2. Main Describe Block
describe('useMyHook', () => {
function renderMyHook(): { } {
}
it('should return initial state', () => {
});
});
Structure Rules:
- Main describe uses the hook name (e.g.,
'useMyHook')
- Contains one
renderMyHook helper function at the top
- All test cases use the
renderMyHook helper
- Tests cover all branches and return values
3. renderMyHook Helper Function
Purpose: Factory function that renders the hook with all necessary context providers and dependencies, allowing for easy overrides in tests.
Pattern with Context:
function renderMyHook(overrides?: {
service?: IService;
}) {
const service = overrides?.service ?? new FakeServiceBuilder().build();
const hookRenderResult = renderHook(() => useMyHook(), {
wrapper: ({ children }) => (
<FakeMyContextProvider service={service}>
{children}
</FakeMyContextProvider>
),
});
return {
hookRenderResult,
service,
};
}
Pattern without Context:
function renderMyHook(params?: {
initialValue?: string;
}) {
const hookRenderResult = renderHook(() => useMyHook(params?.initialValue ?? 'default'));
return {
hookRenderResult,
};
}
Rules:
- Accept
overrides parameter for context dependencies
- Accept
params parameter for hook arguments
- Use nullish coalescing (
??) for default Fake instances
- Use
renderHook with wrapper option for context providers
- Return object with
hookRenderResult and all dependencies
4. Finding Context Dependencies
How to Identify:
- Read the hook file and look for
useContext calls or other context hooks
- Search for the context provider in the codebase
- Check if a Fake version exists in
/fakes folder
- If no Fake exists, create one or use
vi.mock for external dependencies
Multiple Contexts:
Nest providers in the wrapper:
wrapper: ({ children }) => (
<FakeContextA>
<FakeContextB>
{children}
</FakeContextB>
</FakeContextA>
)
5. Writing Test Cases
Structure:
- Use descriptive test names explaining the expected behavior
- Follow Arrange-Act-Assert pattern
- Access hook result via
hookRenderResult.result.current
- Use
act() for state updates
- Use
await act(async () => ...) for async operations
Example:
it('should return expected value', () => {
const { hookRenderResult } = renderMyHook({
service: new FakeServiceBuilder()
.withGetDataReturnValue({ id: '123' })
.build()
});
expect(hookRenderResult.result.current.data).toEqual({ id: '123' });
});
6. Testing State Updates
Always wrap state changes in act():
it('should update state when action is called', () => {
const { hookRenderResult } = renderMyHook();
act(() => {
hookRenderResult.result.current.updateValue('new value');
});
expect(hookRenderResult.result.current.value).toBe('new value');
});
7. Testing Async Operations
Use await act(async () => ...):
it('should save successfully', async () => {
const { hookRenderResult } = renderMyHook();
await act(async () => {
await hookRenderResult.result.current.save();
});
expect(hookRenderResult.result.current.isSaving).toBe(false);
});
8. Mocking Dependencies
Preferred (Fake Providers):
const { hookRenderResult } = renderMyHook({
service: new FakeServiceBuilder()
.withFetchDataReturnValue(Promise.resolve({ success: true }))
.build()
});
When to use vi.mock:
- External React hooks from 3rd party libraries
- Browser APIs
- Modules without Fake implementations
Workflow
Create Workflow
- Identify the hook - Determine which hook to test
- Locate the hook file - Use Glob to find the hook definition
- Read the hook - Understand behavior, return values, context usage
- Identify dependencies - Look for context hooks and other dependencies
- Locate Fakes - Find Fake providers and builders
- Ensure
__tests__ exists - Create folder if needed (sibling to /src)
- Verify @testing-library/react - Check package.json
- Create test file in
__tests__/useMyHook.spec.tsx with:
- All required imports (including React)
- Main describe block
renderMyHook helper function
- Test cases covering all scenarios
Update Workflow
- Read existing test and hook - Compare current state
- Identify changes:
- New return values → Add test cases
- Changed logic → Update test cases
- New dependencies → Update
renderMyHook and imports
- Removed functionality → Remove tests
- Apply updates using Edit tool - Targeted changes only
- Verify coverage - Ensure all logic branches tested
Update Guidelines
- Preserve structure - Use Edit tool, not Write
- Maintain consistency - Follow existing patterns
- Keep descriptive names - Clear, behavior-focused
- Don't delete passing tests - Only update broken/obsolete tests
- Add missing coverage - Test new logic
Best Practices
- Test Behavior, Not Implementation - Focus on return values and side effects
- Descriptive Test Names - Explain expected behavior clearly
- Arrange-Act-Assert - Structure tests consistently
- Use act() for Updates - Always wrap state changes
- Test Edge Cases - Null, undefined, empty values, errors
- Appropriate Matchers - Use correct expect matchers for the assertion
Common Pitfalls to Avoid
- ❌ Don't Render Hook Directly - Always use
renderHook()
- ❌ Don't Forget React Import - Required for JSX in this project
- ❌ Don't Forget act() - State updates need to be wrapped
- ❌ Don't Use vi.mock for Internal - Use Fake providers instead
- ❌ Don't Forget async/await - Async operations need proper handling
Example Reference
See examples.md in the same directory for complete working examples of:
- Simple hooks returning context values
- Hooks with complex logic and multiple return values
- Hooks with parameters
- Hooks with state management
- Standalone hooks without context
- Hooks using multiple contexts
Important Notes
File Organization
- Tests in
__tests__/ at package root (sibling to /src)
- Use
.tsx extension
- Match hook name exactly
Dependencies
- Ensure
@testing-library/react is installed
- Use Fake providers from
/fakes subpath
- Create Fakes if they don't exist
Test Quality
- Many small tests > few large tests
- Test happy path and error cases
- Test edge cases and boundaries
- Descriptive test names
- Simple, focused tests
Running Tests
After creating/updating:
- Run
pnpm test to verify tests pass
- Run
pnpm lint to check linting
- Run
pnpm build to verify TypeScript compiles