testing
Testing Skill
Expert guidance for software testing across multiple frameworks and testing types.
Covered Frameworks
| Framework | Type | Language | Use For |
|---|---|---|---|
| pytest | Unit/Integration | Python | Python backend testing |
| Jest | Unit/Integration | JavaScript/TypeScript | React, Node.js testing |
| Cypress | E2E | JavaScript | Frontend E2E testing |
| Playwright | E2E | Multi-language | Cross-browser E2E testing |
Testing Principles
AAA Pattern (Arrange-Act-Assert)
# Python (pytest)
def test_user_creation():
# Arrange
user_data = {"name": "Alice", "email": "alice@example.com"}
# Act
user = create_user(user_data)
# Assert
assert user.name == "Alice"
assert user.email == "alice@example.com"
// TypeScript (Jest)
describe('UserService', () => {
it('should create user with valid data', () => {
// Arrange
const userData = { name: 'Alice', email: 'alice@example.com' };
// Act
const user = createUser(userData);
// Assert
expect(user.name).toBe('Alice');
expect(user.email).toBe('alice@example.com');
});
});
Test Types
| Type | Scope | Speed | When to Use |
|---|---|---|---|
| Unit | Single function/class | Fast | Business logic, utilities |
| Integration | Multiple components | Medium | API endpoints, database ops |
| E2E | Full user flow | Slow | Critical user journeys |
Coverage Targets
| Type | Target | Priority |
|---|---|---|
| Unit | 80%+ | High |
| Integration | Critical paths | Medium |
| E2E | Happy paths | Medium |
pytest (Python)
Basic Test
import pytest
def test_addition():
assert 1 + 1 == 2
def test_exception():
with pytest.raises(ValueError):
int("not a number")
Fixtures
import pytest
@pytest.fixture
def user():
return User(name="Test User", email="test@example.com")
@pytest.fixture
def db_session():
session = create_session()
yield session
session.rollback()
session.close()
def test_user_save(db_session, user):
db_session.add(user)
db_session.commit()
assert user.id is not None
Parametrized Tests
@pytest.mark.parametrize("input,expected", [
("hello", 5),
("world", 5),
("", 0),
])
def test_string_length(input, expected):
assert len(input) == expected
Async Tests
import pytest
@pytest.mark.asyncio
async def test_async_fetch():
result = await fetch_data()
assert result is not None
Mocking
from unittest.mock import Mock, patch
def test_external_api():
with patch('module.external_api') as mock_api:
mock_api.return_value = {"status": "ok"}
result = call_external_api()
assert result["status"] == "ok"
Conftest for Shared Fixtures
# tests/conftest.py
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
@pytest.fixture(scope="session")
def engine():
return create_engine("sqlite:///:memory:")
@pytest.fixture(scope="function")
def db_session(engine):
connection = engine.connect()
transaction = connection.begin()
session = sessionmaker(bind=connection)()
yield session
session.close()
transaction.rollback()
connection.close()
@pytest.fixture
def client(db_session):
from app import create_app
app = create_app(db_session)
return app.test_client()
Jest (JavaScript/TypeScript)
Basic Test
describe('Math', () => {
it('should add numbers', () => {
expect(1 + 1).toBe(2);
});
it('should throw on invalid input', () => {
expect(() => throwingFunction()).toThrow('Error message');
});
});
Mocking
jest.mock('./api');
import { fetchUser } from './api';
const mockFetchUser = fetchUser as jest.MockedFunction<typeof fetchUser>;
beforeEach(() => {
mockFetchUser.mockResolvedValue({ id: 1, name: 'Test' });
});
it('should fetch user', async () => {
const user = await getUser(1);
expect(user.name).toBe('Test');
});
React Testing Library
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
it('should render button and handle click', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('should handle user input', async () => {
const user = userEvent.setup();
render(<LoginForm onSubmit={mockSubmit} />);
await user.type(screen.getByLabelText(/email/i), 'test@example.com');
await user.type(screen.getByLabelText(/password/i), 'password123');
await user.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(mockSubmit).toHaveBeenCalledWith({
email: 'test@example.com',
password: 'password123',
});
});
});
Async Testing
it('should fetch data asynchronously', async () => {
const data = await fetchData();
expect(data).toBeDefined();
});
it('should resolve promise', async () => {
await expect(asyncFunction()).resolves.toBe('expected value');
});
it('should reject promise', async () => {
await expect(asyncFunction()).rejects.toThrow('error message');
});
Setup and Teardown
describe('Database Tests', () => {
beforeAll(async () => {
await setupDatabase();
});
afterAll(async () => {
await teardownDatabase();
});
beforeEach(async () => {
await clearTables();
});
it('should insert record', async () => {
// Test code
});
});
Cypress (E2E)
Basic Test
describe('Login Flow', () => {
beforeEach(() => {
cy.visit('/login');
});
it('should login with valid credentials', () => {
cy.get('[data-testid="email"]').type('user@example.com');
cy.get('[data-testid="password"]').type('password123');
cy.get('[data-testid="submit"]').click();
cy.url().should('include', '/dashboard');
cy.contains('Welcome').should('be.visible');
});
it('should show error for invalid credentials', () => {
cy.get('[data-testid="email"]').type('wrong@example.com');
cy.get('[data-testid="password"]').type('wrongpassword');
cy.get('[data-testid="submit"]').click();
cy.contains('Invalid credentials').should('be.visible');
cy.url().should('include', '/login');
});
});
Custom Commands
// cypress/support/commands.js
Cypress.Commands.add('login', (email, password) => {
cy.visit('/login');
cy.get('[data-testid="email"]').type(email);
cy.get('[data-testid="password"]').type(password);
cy.get('[data-testid="submit"]').click();
cy.url().should('include', '/dashboard');
});
// Usage in tests
it('should access protected route', () => {
cy.login('user@example.com', 'password123');
cy.visit('/protected');
cy.contains('Protected Content').should('be.visible');
});
API Testing
it('should create user via API', () => {
cy.request('POST', '/api/users', {
name: 'Test User',
email: 'test@example.com'
}).then((response) => {
expect(response.status).to.eq(201);
expect(response.body).to.have.property('id');
});
});
// Intercept and mock API calls
it('should handle API error gracefully', () => {
cy.intercept('GET', '/api/users', {
statusCode: 500,
body: { error: 'Internal Server Error' }
}).as('getUsers');
cy.visit('/users');
cy.wait('@getUsers');
cy.contains('Failed to load users').should('be.visible');
});
Fixtures
// cypress/fixtures/user.json
{
"id": 1,
"name": "Test User",
"email": "test@example.com"
}
// In test
it('should display user data', () => {
cy.fixture('user').then((user) => {
cy.intercept('GET', '/api/user/1', user).as('getUser');
cy.visit('/user/1');
cy.wait('@getUser');
cy.contains(user.name).should('be.visible');
});
});
Playwright (Cross-browser E2E)
Basic Test
import { test, expect } from '@playwright/test';
test('should navigate and login', async ({ page }) => {
await page.goto('/login');
await page.fill('[data-testid="email"]', 'user@example.com');
await page.fill('[data-testid="password"]', 'password123');
await page.click('[data-testid="submit"]');
await expect(page).toHaveURL(/dashboard/);
await expect(page.locator('h1')).toContainText('Welcome');
});
Page Object Model
// pages/LoginPage.ts
import { Page, Locator } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.locator('[data-testid="email"]');
this.passwordInput = page.locator('[data-testid="password"]');
this.submitButton = page.locator('[data-testid="submit"]');
}
async goto() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
}
// In test
test('should login successfully', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.login('user@example.com', 'password123');
await expect(page).toHaveURL(/dashboard/);
});
API Testing
import { test, expect } from '@playwright/test';
test('should create user via API', async ({ request }) => {
const response = await request.post('/api/users', {
data: { name: 'Test', email: 'test@example.com' }
});
expect(response.ok()).toBeTruthy();
expect(await response.json()).toHaveProperty('id');
});
Visual Testing
test('should match snapshot', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage.png');
});
test('should match element snapshot', async ({ page }) => {
await page.goto('/');
const header = page.locator('header');
await expect(header).toHaveScreenshot('header.png');
});
Configuration
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'Mobile Chrome', use: { ...devices['Pixel 5'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
});
Best Practices
- Test behavior, not implementation - Tests should verify what code does, not how
- One assertion per test (when practical) - Makes failures clear
- Use descriptive test names - "should return user when valid ID provided"
- Keep tests independent - No test should depend on another
- Mock external dependencies - Tests should be deterministic
- Use test data builders - Create consistent test data
- Clean up after tests - Reset state, close connections
- Test edge cases - Empty inputs, nulls, boundaries
- Use data-testid attributes - Stable selectors for E2E tests
- Run tests in CI/CD - Catch regressions early
Running Tests
# pytest
pytest # Run all
pytest tests/test_user.py # Run file
pytest -k "test_login" # Run by name pattern
pytest --cov=src # With coverage
pytest -x # Stop on first failure
pytest -v # Verbose output
# Jest
npm test # Run all
npm test -- --watch # Watch mode
npm test -- --coverage # With coverage
npm test -- --testPathPattern="user" # Run specific tests
# Cypress
npx cypress run # Headless
npx cypress open # Interactive
npx cypress run --spec "cypress/e2e/login.cy.js" # Specific file
# Playwright
npx playwright test # Run all
npx playwright test --ui # Interactive UI
npx playwright test --debug # Debug mode
npx playwright test --project=chromium # Specific browser
npx playwright show-report # View HTML report
Test Coverage Tools
| Language | Tool | Command |
|---|---|---|
| Python | pytest-cov | pytest --cov=src |
| JavaScript | Jest | jest --coverage |
| TypeScript | Jest/c8 | jest --coverage |
| E2E | Playwright | Built-in with --coverage |
Coverage Thresholds
// jest.config.js
module.exports = {
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
More from housegarofalo/claude-code-base
mqtt-iot
Configure MQTT brokers (Mosquitto, EMQX) for IoT messaging, device communication, and smart home integration. Manage topics, QoS levels, authentication, and bridging. Use when setting up IoT messaging, smart home communication, or device-to-cloud connectivity. (project)
22devops-engineer-agent
Infrastructure and DevOps specialist. Manages Docker, Kubernetes, CI/CD pipelines, and cloud deployments. Expert in GitHub Actions, Azure DevOps, Terraform, and container orchestration. Use for deployment automation, infrastructure setup, or CI/CD optimization.
6postgresql
Design, optimize, and manage PostgreSQL databases. Covers indexing, pgvector for AI embeddings, JSON operations, full-text search, and query optimization. Use when working with PostgreSQL, database design, or building data-intensive applications.
6home-assistant
Ultimate Home Assistant skill - complete administration, wireless protocols (Zigbee/ZHA/Z2M, Z-Wave JS, Thread, Matter), ESPHome device building, advanced troubleshooting, performance optimization, security hardening, custom integration development, and professional dashboard design. Covers configuration, REST API, automation debugging, database optimization, SSL/TLS, Jinja2 templating, and HACS custom cards. Use for any HA task.
6react-typescript
Build modern React applications with TypeScript. Covers React 18+ patterns, hooks, component architecture, state management (Zustand, Redux Toolkit), server components, and best practices. Use for React development, TypeScript integration, component design, and frontend architecture.
5power-automate
Expert guidance for Power Automate development including cloud flows, desktop flows, Dataverse connector, expression functions, custom connectors, error handling, and child flow patterns. Use when building automated workflows, writing flow expressions, creating custom connectors from OpenAPI, or implementing error handling patterns.
5