"It works on my machine" is not acceptable for tests. A test should produce identical results whether it's running on your laptop, your colleague's Docker container, or GitHub Actions.
// 😱 BAD: Absolute paths that only exist on one machine test('loads config file', () => { const config = loadConfig('/Users/alice/project/config.json'); expect(config.apiUrl).toBe('https://api.example.com'); }); test('saves to temp directory', () => { const file = saveTemp('/tmp/test-output.txt', data); // Fails on Windows: no /tmp directory! expect(fs.existsSync('/tmp/test-output.txt')).toBe(true); }); // ✅ GOOD: Use relative paths and cross-platform abstractions test('loads config file', () => { const config = loadConfig(path.join(__dirname, 'fixtures', 'config.json')); expect(config.apiUrl).toBe('https://api.example.com'); }); test('saves to temp directory', () => { const tempDir = os.tmpdir(); // Works on all platforms const tempFile = path.join(tempDir, 'test-output.txt'); saveTemp(tempFile, data); expect(fs.existsSync(tempFile)).toBe(true); });
// 😱 BAD: Assumes specific tools or services are running test('processes image', () => { // Assumes ImageMagick is installed exec('convert input.jpg -resize 100x100 output.jpg'); // Assumes Redis is running on default port const redis = new Redis({ host: 'localhost', port: 6379 }); redis.set('key', 'value'); }); test('sends email', () => { // Assumes SMTP server on localhost sendEmail({ host: 'localhost', port: 25, to: 'test@example.com' }); }); // ✅ GOOD: Mock or containerize external dependencies test('processes image', () => { // Use a JS library instead of system dependency const sharp = require('sharp'); await sharp('input.jpg').resize(100, 100).toFile('output.jpg'); }); test('caching works', () => { // Use in-memory mock or test container const cache = new Map(); // or use testcontainers for real Redis cache.set('key', 'value'); expect(cache.get('key')).toBe('value'); });
// 😱 BAD: Tests fail in different timezones or at different times test('formats date correctly', () => { const date = new Date('2024-01-15 10:00:00'); expect(formatDate(date)).toBe('Jan 15, 10:00 AM'); // Fails in different timezone! }); test('business hours check', () => { const now = new Date(); // Fails when run at night or weekends! expect(isBusinessHours(now)).toBe(true); }); // ✅ GOOD: Control time and timezone in tests test('formats date correctly', () => { // Use fixed timezone const date = new Date('2024-01-15T10:00:00Z'); expect(formatDate(date, 'UTC')).toBe('Jan 15, 10:00 AM'); }); test('business hours check', () => { // Mock the current time const mockDate = new Date('2024-01-15T14:00:00Z'); // Monday 2 PM jest.spyOn(Date, 'now').mockReturnValue(mockDate.getTime()); expect(isBusinessHours()).toBe(true); const weekend = new Date('2024-01-14T14:00:00Z'); // Sunday Date.now.mockReturnValue(weekend.getTime()); expect(isBusinessHours()).toBe(false); });
// 😱 BAD: Tests fail when offline or API is down test('fetches user data', async () => { const user = await fetch('https://api.github.com/users/octocat'); expect(user.login).toBe('octocat'); // Fails when: offline, API down, rate limited, response changes }); test('geocoding works', async () => { const coords = await geocode('New York'); expect(coords.lat).toBeCloseTo(40.7128); // Fails without internet or API key }); // ✅ GOOD: Mock external APIs test('fetches user data', async () => { // Mock the API call fetchMock.get('https://api.github.com/users/octocat', { login: 'octocat', id: 583231, name: 'The Octocat' }); const user = await fetchGitHubUser('octocat'); expect(user.login).toBe('octocat'); }); // Or use recorded fixtures (like VCR) test('geocoding works', async () => { // Use recorded response const coords = await geocodeWithFixture('New York'); expect(coords.lat).toBeCloseTo(40.7128); });
// Use testcontainers for real services import { GenericContainer } from 'testcontainers'; beforeAll(async () => { const redis = await new GenericContainer('redis') .withExposedPorts(6379) .start(); process.env.REDIS_URL = `redis://${redis.getHost()}:${redis.getMappedPort(6379)}`; });
// .env.test DATABASE_URL=sqlite::memory: API_KEY=test-key-123 TEMP_DIR=./test-temp // test setup require('dotenv').config({ path: '.env.test' }); // Now tests work everywhere with same config
// Platform abstraction class FileSystem { static getTempDir() { return process.platform === 'win32' ? process.env.TEMP : '/tmp'; } static getPathSeparator() { return path.sep; // Handles / vs \ } static normalize(filepath) { return path.normalize(filepath); } }
// package.json { "scripts": { "test": "jest", "test:ci": "jest --ci --coverage", "test:setup": "docker-compose up -d test-deps" }, "engines": { "node": ">=18.0.0" } } // README.md ## Running Tests ```bash # Install dependencies npm install # Start test containers (PostgreSQL, Redis) npm run test:setup # Run tests npm test ```
# GitHub Actions example strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] node: [18, 20, 22] runs-on: ${{ matrix.os }} steps: - uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - run: npm test