testing-frameworks
Testing Frameworks - Jest, Vitest, Pytest
Write maintainable, reliable tests with proper structure and mocking
When to Use This Skill
Use this skill when:
- Writing unit, integration, or end-to-end tests
- Setting up test frameworks
- Implementing mocks and fixtures
- Following TDD practices
Critical Patterns
Pattern 1: AAA Pattern
When: Structuring all tests
Good:
test('should apply 10% discount for orders over $100', () => {
// Arrange
const order = { items: [{ price: 120 }], discount: 0.1 };
// Act
const total = calculateTotal(order);
// Assert
expect(total).toBe(108);
});
Why: AAA (Arrange, Act, Assert) makes tests clear and maintainable.
Pattern 2: Mocking Dependencies
When: Isolating units under test
Good (Jest):
import { getUserById } from './userService';
import { fetchUser } from './api';
jest.mock('./api');
test('should fetch and return user', async () => {
// Arrange
const mockUser = { id: 1, name: 'Alice' };
(fetchUser as jest.Mock).mockResolvedValue(mockUser);
// Act
const user = await getUserById(1);
// Assert
expect(fetchUser).toHaveBeenCalledWith(1);
expect(user).toEqual(mockUser);
});
Good (Pytest):
def test_get_user(mocker):
# Arrange
mock_get = mocker.patch('requests.get')
mock_get.return_value.json.return_value = {"id": 1, "name": "Alice"}
# Act
user = get_user(1)
# Assert
assert user["name"] == "Alice"
Why: Mocking isolates the unit under test and makes tests fast and reliable.
Pattern 3: Test Organization
When: Grouping related tests
Good:
describe('calculateTotal', () => {
test('applies discount for orders over $100', () => {
expect(calculateTotal({ price: 120, discount: 0.1 })).toBe(108);
});
test('handles zero discount', () => {
expect(calculateTotal({ price: 50, discount: 0 })).toBe(50);
});
test('throws error for negative prices', () => {
expect(() => calculateTotal({ price: -10 })).toThrow();
});
});
Why: Grouping related tests improves organization and readability.
Pattern 4: Async Testing
When: Testing asynchronous code
Good:
// async/await
test('should fetch user data', async () => {
const data = await fetchUser(1);
expect(data.name).toBe('Alice');
});
// .resolves/.rejects
test('should fetch user data', () => {
return expect(fetchUser(1)).resolves.toHaveProperty('name', 'Alice');
});
test('should handle errors', () => {
return expect(fetchUser(999)).rejects.toThrow('User not found');
});
Why: Properly handling async ensures tests wait for operations to complete.
Pattern 5: Fixtures (Pytest)
When: Reusing test data
Good:
@pytest.fixture
def user():
return {"id": 1, "name": "Alice"}
@pytest.fixture
def database():
db = Database()
db.connect()
yield db
db.disconnect()
def test_save_user(database, user):
database.save(user)
assert database.count() == 1
Why: Fixtures provide reusable setup and teardown logic.
Best Practices
One Assertion Per Test
// ✅ Good: Separate tests
test('user has correct name', () => {
expect(user.name).toBe('Alice');
});
test('user email is valid', () => {
expect(user.email).toContain('@');
});
// ❌ Bad: Multiple unrelated assertions
test('user validation', () => {
expect(user.name).toBe('Alice');
expect(user.email).toContain('@');
expect(user.age).toBeGreaterThan(0);
});
Code Examples
Example 1: Unit Test with Mocking (Jest/Vitest)
import { getUserById, updateUser } from './userService';
import { db } from './db';
jest.mock('./db');
describe('userService', () => {
test('should fetch user by id', async () => {
// Arrange
const mockUser = { id: 1, name: 'Alice', email: 'alice@example.com' };
(db.user.findUnique as jest.Mock).mockResolvedValue(mockUser);
// Act
const user = await getUserById(1);
// Assert
expect(db.user.findUnique).toHaveBeenCalledWith({ where: { id: 1 } });
expect(user).toEqual(mockUser);
});
test('should throw error when user not found', async () => {
// Arrange
(db.user.findUnique as jest.Mock).mockResolvedValue(null);
// Act & Assert
await expect(getUserById(999)).rejects.toThrow('User not found');
});
});
Example 2: Component Test with User Interaction
import { render, screen, fireEvent } from '@testing-library/react';
import { LoginForm } from './LoginForm';
test('should display error on invalid email', async () => {
// Arrange
render(<LoginForm />);
// Act
const emailInput = screen.getByLabelText(/email/i);
const submitButton = screen.getByRole('button', { name: /submit/i });
fireEvent.change(emailInput, { target: { value: 'invalid-email' } });
fireEvent.click(submitButton);
// Assert
expect(await screen.findByText(/invalid email/i)).toBeInTheDocument();
});
For comprehensive examples and detailed implementations, see the references/ folder.
Quick Reference
Jest/Vitest Commands
npm test # Run all tests
npm test -- --watch # Watch mode
npm test -- --coverage # Coverage report
Pytest Commands
pytest # Run all tests
pytest -v # Verbose
pytest --cov=myapp # Coverage
pytest -k "user" # Run tests matching "user"
Progressive Disclosure
For detailed implementations:
- Jest & Vitest Guide - Matchers, mocking, setup/teardown
- Pytest Guide - Fixtures, parametrized tests, mocking
References
Maintained by dsmj-ai-toolkit