--- name: testing-patterns description: | Testing patterns for JavaScript/TypeScript and Python. pytest, Jest, mocking, fixtures, TDD patterns. Use when writing tests or setting up test infrastructure. --- # Testing Patterns Comprehensive testing patterns for JavaScript/TypeScript and Python. --- ## Quick Reference | Language | Framework | Use Section | |----------|-----------|-------------| | JavaScript/TypeScript | Jest, Vitest | **JavaScript Testing** | | Python | pytest | **Python Testing** | | Shell | bats | **Shell Testing** | --- ## JavaScript/TypeScript Testing ### Frameworks - **Jest** - Most popular, built-in mocking - **Vitest** - Vite-native, fast, compatible with Jest - **Mocha** - Flexible, requires external assertions ### Jest Setup ```javascript // jest.config.js module.exports = { testEnvironment: 'node', testMatch: ['**/__tests__/**/*.test.js'], collectCoverage: true, coveragePathIgnorePatterns: ['/node_modules/'], setupFilesAfterEnv: ['/jest.setup.js'], }; ``` ### Basic Test Structure ```javascript describe('Calculator', () => { let calculator; beforeEach(() => { calculator = new Calculator(); }); it('should add two numbers', () => { expect(calculator.add(2, 3)).toBe(5); }); it('should throw for invalid input', () => { expect(() => calculator.add('a', 3)).toThrow(TypeError); }); }); ``` ### Mocking ```javascript // Mock a function const mockFn = jest.fn(); mockFn.mockReturnValue(42); // Mock a module jest.mock('./api', () => ({ fetchUser: jest.fn().mockResolvedValue({ id: 1, name: 'John' }), })); // Spy on a method const spy = jest.spyOn(calculator, 'add'); calculator.add(2, 3); expect(spy).toHaveBeenCalledWith(2, 3); ``` ### Async Testing ```javascript it('should fetch user', async () => { const user = await fetchUser(1); expect(user.name).toBe('John'); }); it('should handle error', async () => { await expect(fetchUser(-1)).rejects.toThrow('Not found'); }); ``` ### React Component Testing ```javascript import { render, screen, fireEvent } from '@testing-library/react'; test('should render login form', () => { render(); expect(screen.getByLabelText('Email')).toBeInTheDocument(); fireEvent.change(screen.getByLabelText('Email'), { target: { value: 'test@example.com' }, }); }); ``` ### Vitest (Modern Alternative) ```javascript import { describe, it, expect, beforeEach } from 'vitest'; import { mount } from '@vue/test-utils'; describe('Counter', () => { it('increments', async () => { const wrapper = mount(Counter); await wrapper.find('button').trigger('click'); expect(wrapper.text()).toContain('1'); }); }); ``` --- ## Python Testing (pytest) ### pytest Setup ```bash pip install pytest pytest-cov pytest-mock pytest-asyncio ``` ### Basic Test Structure ```python import pytest class TestCalculator: @pytest.fixture def calculator(self): return Calculator() def test_add(self, calculator): assert calculator.add(2, 3) == 5 def test_invalid_input(self, calculator): with pytest.raises(TypeError): calculator.add("a", 3) ``` ### Fixtures ```python @pytest.fixture def user(): return User(name="John", email="john@example.com") @pytest.fixture def db_session(): session = create_session() yield session session.close() def test_user(db_session, user): db_session.add(user) db_session.commit() assert db_session.query(User).count() == 1 ``` ### Mocking ```python from unittest.mock import Mock, patch @patch('api.fetch_user') def test_fetch_user(mock_fetch): mock_fetch.return_value = {"id": 1, "name": "John"} result = get_user(1) assert result["name"] == "John" mock_fetch.assert_called_once_with(1) # Async mocking @pytest.mark.asyncio @patch('api.async_fetch_user') async def test_async_fetch(mock_fetch): mock_fetch.return_value = {"id": 1} result = await get_user(1) assert result["id"] == 1 ``` ### Parameterized Tests ```python @pytest.mark.parametrize("input,expected", [ (2, 4), (3, 9), (4, 16), ]) def test_square(input, expected): assert square(input) == expected ``` ### Testing Exceptions ```python def test_raises(): with pytest.raises(ValueError, match="must be positive"): factorial(-1) ``` ### Database Testing ```python @pytest.fixture def test_db(): engine = create_test_engine() Base.metadata.create_all(engine) yield session Base.metadata.drop_all(engine) def test_user_crud(test_db): user = User(name="John") test_db.add(user) test_db.commit() retrieved = test_db.query(User).first() assert retrieved.name == "John" ``` ### Async Testing ```python pytestmark = pytest.mark.asyncio async def test_async_fetch(): result = await fetch_data() assert result is not None ``` --- ## Shell Testing (BATS) ### BATS Setup ```bash bats my_tests.bats ``` ### Basic Test Structure ```bash #!/usr/bin/env bats @test "addition" { result=$(echo "2 + 3" | bc) [ "$result" -eq 5 ] } @test "file exists" { touch /tmp/testfile [ -f /tmp/testfile ] } @test "command succeeds" { run ls /tmp [ "$status" -eq 0 ] } ``` ### Setup/Teardown ```bash setup() { echo "Before each test" } teardown() { echo "After each test" } ``` --- ## Testing Best Practices ### Test Naming ```javascript // Good describe('UserService', () => { it('should return null for non-existent user'); it('should hash password before storing'); it('should throw ValidationError for invalid email'); }); // Bad it('test1'); it('user test'); ``` ### AAA Pattern ```javascript // Arrange - Set up test data const user = { name: 'John', email: 'john@example.com' }; // Act - Execute the code const result = userService.create(user); // Assert - Verify the outcome expect(result.id).toBeDefined(); expect(result.password).not.toBe('plaintext'); ``` ### Test Isolation ```javascript // Bad - shared state let user; it('creates', () => { user = create(); }); it('modifies', () => { modify(user.id); }); // depends on first test // Good - isolated it('creates and modifies', () => { const user = create(); modify(user.id); }); ``` ### Meaningful Assertions ```javascript // Bad expect(result).toBeDefined(); // Good expect(result).toEqual({ id: '123', name: 'John', email: 'john@example.com' }); ``` ### Fast Tests - Mock external APIs - Use in-memory databases - Parallel test execution - Avoid `sleep()` - use explicit waits --- ## Mocking Patterns ### HTTP Requests (JavaScript) ```javascript // Mock fetch global.fetch = jest.fn().mockResolvedValue({ ok: true, json: () => Promise.resolve({ data: 'test' }), }); // Mock axios jest.mock('axios'); axios.get.mockResolvedValue({ data: { id: 1 } }); ``` ### HTTP Requests (Python) ```python import requests_mock from requests_mock import DELETE, GET, POST def test_api(requests_mock): requests_mock.register_uri(GET, 'https://api.example.com/user', json={'id': 1, 'name': 'John'}) response = requests.get('https://api.example.com/user') assert response.json()['name'] == 'John' ``` ### Time/Mocking ```javascript // Jest jest.useFakeTimers(); jest.setSystemTime(new Date('2024-01-01')); // ... test code jest.useRealTimers(); ``` ```python # Python from freezegun import freeze_time @freeze_time("2024-01-01") def test_time_based(): assert get_current_year() == 2024 ``` --- ## Coverage ### Jest Coverage ```javascript // jest.config.js module.exports = { collectCoverage: true, coverageThreshold: { global: { branches: 70, functions: 70, lines: 70, statements: 70, }, }, }; ``` ### pytest Coverage ```bash pytest --cov=src --cov-report=html --cov-fail-under=70 ```