---
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: ['<rootDir>/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(<LoginForm />);
  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
```
