TEST TEMPLATE
/** * Test Template - Use this as a reference when writing new tests * * Place this file next to your component/hook/utility with a .test.ts(x) suffix * Example: MyComponent.tsx -> MyComponent.test.tsx */
// ============================================================================ // HOOK TEST TEMPLATE // ============================================================================
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { renderHook, act, waitFor } from '@testing-library/react'; import { useMyHook } from '@/src/hooks/use-my-hook';
describe('useMyHook', () => { beforeEach(() => { vi.clearAllMocks(); });
afterEach(() => { vi.restoreAllMocks(); });
it('should initialize with default value', () => { const { result } = renderHook(() => useMyHook()); expect(result.current).toEqual(expectedDefault); });
it('should update state when action is performed', async () => { const { result } = renderHook(() => useMyHook());
await act(async () => {
result.current.doSomething();
});
expect(result.current.state).toBe(expectedState);
});
it('should cleanup on unmount', () => { const cleanupSpy = vi.spyOn(global, 'clearInterval'); const { unmount } = renderHook(() => useMyHook());
unmount();
expect(cleanupSpy).toHaveBeenCalled();
cleanupSpy.mockRestore();
}); });
// ============================================================================ // COMPONENT TEST TEMPLATE // ============================================================================
import { render, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { MyComponent } from '@/src/components/MyComponent';
describe('MyComponent', () => {
it('should render component with expected content', () => {
render(
expect(screen.getByText('Test Title')).toBeInTheDocument();
});
it('should handle user interactions', async () => { const user = userEvent.setup(); const handleClick = vi.fn();
render(<MyComponent onClick={handleClick} />);
const button = screen.getByRole('button');
await user.click(button);
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('should accept custom className', () => {
const { container } = render(
it('should render children', () => {
render(
expect(screen.getByText('Child content')).toBeInTheDocument();
});
it('should be accessible', () => {
render(
// ============================================================================ // UTILITY/SERVICE TEST TEMPLATE // ============================================================================
import { myUtility, asyncUtility } from '@/src/lib/my-utility';
// Mock external dependencies vi.mock('@/src/services/externalService', () => ({ externalCall: vi.fn(), }));
import { externalCall } from '@/src/services/externalService';
describe('myUtility', () => { beforeEach(() => { vi.clearAllMocks(); });
it('should process input correctly', () => { const result = myUtility('input'); expect(result).toBe('expected output'); });
it('should handle edge cases', () => { expect(myUtility('')).toBe('default'); expect(myUtility(null)).toBe('default'); });
it('should call external service correctly', async () => { (externalCall as any).mockResolvedValue({ data: 'response' });
const result = await asyncUtility('input');
expect(externalCall).toHaveBeenCalledWith('input');
expect(result).toEqual({ data: 'response' });
});
it('should handle errors gracefully', async () => { (externalCall as any).mockRejectedValue(new Error('Network error'));
const result = await asyncUtility('input');
expect(result).toBeNull(); // or your error handling behavior
}); });
// ============================================================================ // FORM COMPONENT TEST TEMPLATE // ============================================================================
import { MyForm } from '@/src/components/forms/MyForm';
describe('MyForm', () => {
it('should validate required fields', async () => {
const user = userEvent.setup();
render(
await user.click(screen.getByRole('button', { name: /submit/i }));
expect(screen.getByText(/email is required/i)).toBeInTheDocument();
});
it('should submit form with valid data', async () => { const user = userEvent.setup(); const handleSubmit = vi.fn();
render(<MyForm onSubmit={handleSubmit} />);
const emailInput = screen.getByLabelText(/email/i);
await user.type(emailInput, 'test@example.com');
await user.click(screen.getByRole('button', { name: /submit/i }));
expect(handleSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
});
});
it('should show loading state during submission', async () => {
const user = userEvent.setup();
render(
await user.click(screen.getByRole('button', { name: /submit/i }));
expect(screen.getByRole('button')).toBeDisabled();
}); });
// ============================================================================ // COMMON TESTING PATTERNS // ============================================================================
describe('Common Patterns', () => { // Testing async state changes it('should handle async state updates', async () => { const { result } = renderHook(() => useMyAsyncHook());
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.data).toBeDefined();
});
// Testing with providers
it('should work with context provider', () => {
render(
expect(screen.getByText('test')).toBeInTheDocument();
});
// Testing error boundaries it('should catch errors in error boundary', () => { // Suppress console.error for this test vi.spyOn(console, 'error').mockImplementation(() => {});
render(
<ErrorBoundary>
<ThrowingComponent />
</ErrorBoundary>
);
expect(screen.getByText(/something went wrong/i)).toBeInTheDocument();
});
// Testing with fake timers it('should handle debounced input', () => { vi.useFakeTimers();
const { result } = renderHook(() => useDebounce('test', 500));
act(() => {
vi.advanceTimersByTime(500);
});
expect(result.current).toBe('test');
vi.useRealTimers();
}); });