Testing Framework Helper Skill
Expert at creating comprehensive test suites following testing best practices.
When to Activate
-
"write tests for [component/function]"
-
"create test suite for [feature]"
-
"generate unit/integration/E2E tests"
Jest/Vitest (JavaScript/TypeScript)
// UserService.test.ts import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { UserService } from './UserService'; import { mockDatabase } from '../test-utils/mockDatabase';
describe('UserService', () => { let userService: UserService; let db: ReturnType<typeof mockDatabase>;
beforeEach(() => { db = mockDatabase(); userService = new UserService(db); });
afterEach(() => { vi.clearAllMocks(); });
describe('getById', () => { it('should return user when found', async () => { const mockUser = { id: 1, name: 'John Doe', email: 'john@example.com', };
db.user.findUnique.mockResolvedValue(mockUser);
const result = await userService.getById(1);
expect(result).toEqual(mockUser);
expect(db.user.findUnique).toHaveBeenCalledWith({
where: { id: 1 },
});
});
it('should return null when user not found', async () => {
db.user.findUnique.mockResolvedValue(null);
const result = await userService.getById(999);
expect(result).toBeNull();
});
it('should throw error on database failure', async () => {
db.user.findUnique.mockRejectedValue(new Error('DB Error'));
await expect(userService.getById(1)).rejects.toThrow('DB Error');
});
});
describe('create', () => { it('should create user with valid data', async () => { const userData = { name: 'Jane Doe', email: 'jane@example.com', password: 'SecurePass123!', };
const createdUser = {
id: 2,
...userData,
password: 'hashed_password',
};
db.user.create.mockResolvedValue(createdUser);
const result = await userService.create(userData);
expect(result).toEqual(createdUser);
expect(db.user.create).toHaveBeenCalledWith({
data: expect.objectContaining({
name: userData.name,
email: userData.email,
}),
});
});
it('should throw error when email exists', async () => {
const userData = {
name: 'John Doe',
email: 'existing@example.com',
password: 'pass123',
};
db.user.findUnique.mockResolvedValue({ id: 1 });
await expect(userService.create(userData)).rejects.toThrow(
'Email already exists'
);
});
}); });
React Testing Library
// UserList.test.tsx import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { UserList } from './UserList'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const mockUsers = [ { id: 1, name: 'Alice', email: 'alice@test.com' }, { id: 2, name: 'Bob', email: 'bob@test.com' }, ];
const createWrapper = () => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, });
return ({ children }) => ( <QueryClientProvider client={queryClient}> {children} </QueryClientProvider> ); };
describe('UserList', () => { beforeEach(() => { global.fetch = vi.fn(); });
it('renders loading state initially', () => { (global.fetch as any).mockImplementation(() => new Promise(() => {}));
render(<UserList />, { wrapper: createWrapper() });
expect(screen.getByText(/loading/i)).toBeInTheDocument();
});
it('renders user list after loading', async () => { (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => mockUsers, });
render(<UserList />, { wrapper: createWrapper() });
await waitFor(() => {
expect(screen.getByText('Alice')).toBeInTheDocument();
expect(screen.getByText('Bob')).toBeInTheDocument();
});
});
it('handles search functionality', async () => { render(<UserList />, { wrapper: createWrapper() });
const searchInput = screen.getByPlaceholderText(/search/i);
fireEvent.change(searchInput, { target: { value: 'Alice' } });
await waitFor(() => {
expect(global.fetch).toHaveBeenCalledWith(
expect.stringContaining('search=Alice')
);
});
});
it('handles error state', async () => { (global.fetch as any).mockRejectedValueOnce(new Error('API Error'));
render(<UserList />, { wrapper: createWrapper() });
await waitFor(() => {
expect(screen.getByText(/error/i)).toBeInTheDocument();
});
}); });
Pytest (Python)
test_user_service.py
import pytest from unittest.mock import Mock, patch, AsyncMock from app.services.user_service import UserService from app.models.user import User from app.exceptions import ValidationError, NotFoundError
@pytest.fixture def user_service(): """Fixture for UserService instance""" return UserService()
@pytest.fixture def mock_user(): """Fixture for mock user data""" return User( id=1, email="test@example.com", name="Test User", role="user" )
class TestUserService: @pytest.mark.asyncio async def test_get_by_id_success(self, user_service, mock_user): """Test getting user by ID successfully""" with patch.object(User, 'get', return_value=mock_user) as mock_get: result = await user_service.get_by_id(1)
assert result == mock_user
mock_get.assert_called_once_with(id=1)
@pytest.mark.asyncio
async def test_get_by_id_not_found(self, user_service):
"""Test getting non-existent user"""
with patch.object(User, 'get', return_value=None):
with pytest.raises(NotFoundError, match="User not found"):
await user_service.get_by_id(999)
@pytest.mark.asyncio
async def test_create_success(self, user_service):
"""Test creating user successfully"""
user_data = {
"email": "new@example.com",
"name": "New User",
"password": "SecurePass123!"
}
with patch.object(User, 'create', return_value=Mock(id=2)) as mock_create:
result = await user_service.create(user_data)
assert result.id == 2
mock_create.assert_called_once()
@pytest.mark.asyncio
async def test_create_duplicate_email(self, user_service, mock_user):
"""Test creating user with existing email"""
user_data = {
"email": "existing@example.com",
"name": "Test",
"password": "pass123"
}
with patch.object(User, 'get_by_email', return_value=mock_user):
with pytest.raises(ValidationError, match="Email already exists"):
await user_service.create(user_data)
@pytest.mark.parametrize("email,valid", [
("valid@example.com", True),
("invalid-email", False),
("", False),
("test@test", False),
])
def test_validate_email(self, user_service, email, valid):
"""Test email validation with various inputs"""
if valid:
user_service.validate_email(email)
else:
with pytest.raises(ValidationError):
user_service.validate_email(email)
Integration Tests
// user.integration.test.ts import request from 'supertest'; import { app } from '../app'; import { setupTestDB, cleanupTestDB, seedTestData } from '../test-utils';
describe('User API Integration Tests', () => { let authToken: string;
beforeAll(async () => { await setupTestDB(); const seedData = await seedTestData(); authToken = seedData.adminToken; });
afterAll(async () => { await cleanupTestDB(); });
describe('GET /api/users', () => {
it('should return paginated users', async () => {
const response = await request(app)
.get('/api/users')
.set('Authorization', Bearer ${authToken})
.query({ page: 1, limit: 10 })
.expect(200);
expect(response.body).toHaveProperty('users');
expect(response.body).toHaveProperty('meta');
expect(Array.isArray(response.body.users)).toBe(true);
expect(response.body.meta).toMatchObject({
page: 1,
limit: 10,
});
});
it('should require authentication', async () => {
await request(app)
.get('/api/users')
.expect(401);
});
});
describe('POST /api/users', () => { it('should create new user with valid data', async () => { const userData = { name: 'New User', email: 'newuser@test.com', password: 'SecurePass123!', };
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(201);
expect(response.body.data).toMatchObject({
name: userData.name,
email: userData.email,
});
expect(response.body.data).not.toHaveProperty('password');
});
it('should reject invalid email', async () => {
const userData = {
name: 'Test',
email: 'invalid-email',
password: 'pass123',
};
const response = await request(app)
.post('/api/users')
.send(userData)
.expect(400);
expect(response.body).toHaveProperty('error');
});
}); });
E2E Tests (Playwright/Cypress)
// e2e/user-management.spec.ts import { test, expect } from '@playwright/test';
test.describe('User Management', () => { test.beforeEach(async ({ page }) => { await page.goto('/login'); await page.fill('[name="email"]', 'admin@test.com'); await page.fill('[name="password"]', 'admin123'); await page.click('button[type="submit"]'); await expect(page).toHaveURL('/dashboard'); });
test('should display user list', async ({ page }) => { await page.goto('/users');
await expect(page.locator('h1')).toContainText('Users');
await expect(page.locator('[data-testid="user-row"]')).toHaveCount(10);
});
test('should create new user', async ({ page }) => { await page.goto('/users'); await page.click('[data-testid="add-user-button"]');
await page.fill('[name="name"]', 'Test User');
await page.fill('[name="email"]', 'test@example.com');
await page.fill('[name="password"]', 'SecurePass123!');
await page.click('[data-testid="submit-button"]');
await expect(page.locator('.success-message')).toBeVisible();
await expect(page.locator('text=Test User')).toBeVisible();
});
test('should search users', async ({ page }) => { await page.goto('/users');
await page.fill('[data-testid="search-input"]', 'Alice');
await page.waitForLoadState('networkidle');
const rows = page.locator('[data-testid="user-row"]');
await expect(rows).toHaveCount(1);
await expect(rows.first()).toContainText('Alice');
}); });
Best Practices
-
Follow AAA pattern (Arrange, Act, Assert)
-
Use descriptive test names
-
Test edge cases and error conditions
-
Mock external dependencies
-
Use fixtures for test data
-
Clean up after tests
-
Test both happy and unhappy paths
-
Use parameterized tests for similar cases
-
Maintain test independence
-
Keep tests fast
-
Use proper assertion libraries
-
Test accessibility
-
Use test coverage tools
-
Avoid test interdependence
Output Checklist
-
✅ Test file created
-
✅ Setup/teardown configured
-
✅ Unit tests written
-
✅ Integration tests (if needed)
-
✅ E2E tests (if needed)
-
✅ Mocks/fixtures created
-
✅ Edge cases covered
-
📝 Test documentation