Jess Cheatsheet¶
Jest - Delightful JavaScript Testing
Jest ist ein reizvolles JavaScript-Testgerüst mit einem Fokus auf Einfachheit. Es funktioniert aus der Box für die meisten JavaScript-Projekte und bietet Funktionen wie Snapshot-Tests, eingebauten Test-Läufer, Durchsetzungsbibliothek und leistungsstarke Mocking-Funktionen. < p>
Inhaltsverzeichnis¶
- [Installation](#installation
- (#getting-started)
- (#basic-testing_)
- Matcher
- [Async Testing](LINK_4_
- (Mocking)(LINK_5_)
- (#snapshot-testing_)
- [Konfiguration](#configuration
- (#react-testing_)
- [Code Coverage](#code-coverage
- [Erweiterte Funktionen](#advanced-features
- (#testing-patterns)
- [Performance](#performance_
- [Integration](LINK_13__
- (#troubleshooting_)
- Beste Praktiken
Installation¶
Einfache Installation¶
# Install Jest
npm install --save-dev jest
# Install with TypeScript support
npm install --save-dev jest @types/jest ts-jest
# Install with Babel support
npm install --save-dev jest babel-jest @babel/core @babel/preset-env
# Global installation (not recommended)
npm install -g jest
```_
### React Projects
```bash
# Create React App (Jest included)
npx create-react-app my-app
cd my-app
# Manual React setup
npm install --save-dev jest @testing-library/react @testing-library/jest-dom
# React Native
npm install --save-dev jest react-test-renderer
```_
### Paket.json Konfiguration
```json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:ci": "jest --ci --coverage --watchAll=false"
},
"devDependencies": {
"jest": "^29.0.0",
"@testing-library/jest-dom": "^5.16.0"
}
}
```_
### Projektstruktur
Erste Schritte¶
Erster Test¶
```javascript // math.js export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }
export function multiply(a, b) { return a * b; }
export function divide(a, b) { if (b === 0) { throw new Error('Division by zero'); } return a / b; } ```_
```javascript // math.test.js import { add, subtract, multiply, divide } from './math';
describe('Math functions', () => { test('adds 1 + 2 to equal 3', () => { expect(add(1, 2)).toBe(3); });
test('subtracts 5 - 3 to equal 2', () => { expect(subtract(5, 3)).toBe(2); });
test('multiplies 3 * 4 to equal 12', () => { expect(multiply(3, 4)).toBe(12); });
test('divides 8 / 2 to equal 4', () => { expect(divide(8, 2)).toBe(4); });
test('throws error when dividing by zero', () => { expect(() => divide(10, 0)).toThrow('Division by zero'); }); }); ```_
Laufende Prüfungen¶
```bash
Run all tests¶
npm test
Run tests in watch mode¶
npm run test:watch
Run specific test file¶
npm test math.test.js
Run tests matching pattern¶
npm test -- --testNamePattern="add"
Run tests with coverage¶
npm run test:coverage
Run tests in CI mode¶
npm run test:ci ```_
Prüforganisation¶
```javascript // Grouping tests with describe describe('Calculator', () => { describe('Addition', () => { test('should add positive numbers', () => { expect(add(2, 3)).toBe(5); });
test('should add negative numbers', () => {
expect(add(-2, -3)).toBe(-5);
});
test('should add zero', () => {
expect(add(5, 0)).toBe(5);
});
});
describe('Division', () => { test('should divide positive numbers', () => { expect(divide(10, 2)).toBe(5); });
test('should handle division by zero', () => {
expect(() => divide(10, 0)).toThrow();
});
}); }); ```_
Grundlagenprüfung¶
Prüfstruktur¶
```javascript // Basic test structure test('description of what is being tested', () => { // Arrange - set up test data const input = 'hello'; const expected = 'HELLO';
// Act - execute the function const result = input.toUpperCase();
// Assert - verify the result expect(result).toBe(expected); });
// Alternative syntax it('should convert string to uppercase', () => { expect('hello'.toUpperCase()).toBe('HELLO'); }); ```_
Setup und Teardown¶
```javascript describe('Database tests', () => { let database;
// Run before all tests in this describe block beforeAll(async () => { database = await connectToDatabase(); });
// Run after all tests in this describe block afterAll(async () => { await database.close(); });
// Run before each test beforeEach(() => { database.clear(); });
// Run after each test afterEach(() => { database.cleanup(); });
test('should save user', async () => { const user = { name: 'John', email: 'john@example.com' }; await database.save(user);
const savedUser = await database.findByEmail('john@example.com');
expect(savedUser).toEqual(user);
}); }); ```_
Skipping- und Fokustests¶
```javascript // Skip tests describe.skip('Skipped test suite', () => { test('this will not run', () => { expect(true).toBe(false); }); });
test.skip('skipped test', () => { expect(true).toBe(false); });
// Focus on specific tests (only these will run) describe.only('Focused test suite', () => { test('this will run', () => { expect(true).toBe(true); }); });
test.only('focused test', () => { expect(true).toBe(true); });
// Conditional tests const runIntegrationTests = process.env.NODE_ENV === 'test';
(runIntegrationTests ? describe : describe.skip)('Integration tests', () => { test('should integrate with external service', () => { // Integration test code }); }); ```_
Parametertests¶
```javascript // Test with multiple inputs describe('isPrime function', () => { test.each([ [2, true], [3, true], [4, false], [5, true], [6, false], [7, true], [8, false], [9, false], [10, false], [11, true], ])('isPrime(%i) should return %s', (input, expected) => { expect(isPrime(input)).toBe(expected); }); });
// Test with objects describe('user validation', () => { test.each([ { name: 'John', email: 'john@example.com', valid: true }, { name: '', email: 'john@example.com', valid: false }, { name: 'John', email: 'invalid-email', valid: false }, { name: 'John', email: '', valid: false }, ])('validateUser($name, $email) should return $valid', ({ name, email, valid }) => { expect(validateUser({ name, email })).toBe(valid); }); }); ```_
Spiele¶
Grundlegende Spiele¶
```javascript describe('Basic matchers', () => { test('equality matchers', () => { expect(2 + 2).toBe(4); // Exact equality (Object.is) expect({ name: 'John' }).toEqual({ name: 'John' }); // Deep equality expect({ name: 'John' }).not.toBe({ name: 'John' }); // Different objects });
test('truthiness matchers', () => { expect(true).toBeTruthy(); expect(false).toBeFalsy(); expect(null).toBeNull(); expect(undefined).toBeUndefined(); expect('hello').toBeDefined(); });
test('number matchers', () => { expect(2 + 2).toBeGreaterThan(3); expect(2 + 2).toBeGreaterThanOrEqual(4); expect(2 + 2).toBeLessThan(5); expect(2 + 2).toBeLessThanOrEqual(4); expect(0.1 + 0.2).toBeCloseTo(0.3); // Floating point }); }); ```_
String Matches¶
```javascript describe('String matchers', () => { test('string matching', () => { expect('Hello World').toMatch(/World/); expect('Hello World').toMatch('World'); expect('user@example.com').toMatch(/^[\w.-]+@[\w.-]+.\w+$/);
expect('Hello World').toContain('World');
expect('Hello World').not.toContain('Goodbye');
expect('Hello World').toHaveLength(11);
expect('').toHaveLength(0);
}); }); ```_
Array und Object Matches¶
```javascript describe('Array and object matchers', () => { test('array matchers', () => { const fruits = ['apple', 'banana', 'orange'];
expect(fruits).toContain('banana');
expect(fruits).toHaveLength(3);
expect(fruits).toEqual(['apple', 'banana', 'orange']);
expect(fruits).toEqual(expect.arrayContaining(['apple', 'banana']));
});
test('object matchers', () => { const user = { id: 1, name: 'John', email: 'john@example.com', profile: { age: 30, city: 'New York' } };
expect(user).toHaveProperty('name');
expect(user).toHaveProperty('profile.age', 30);
expect(user).toMatchObject({
name: 'John',
email: 'john@example.com'
});
expect(user).toEqual(expect.objectContaining({
name: 'John',
id: expect.any(Number)
}));
}); }); ```_
Außergewöhnliche Spiele¶
```javascript describe('Exception matchers', () => { test('function throws', () => { const throwError = () => { throw new Error('Something went wrong'); };
expect(throwError).toThrow();
expect(throwError).toThrow('Something went wrong');
expect(throwError).toThrow(/wrong/);
expect(throwError).toThrow(Error);
});
test('async function throws', async () => { const asyncThrowError = async () => { throw new Error('Async error'); };
await expect(asyncThrowError()).rejects.toThrow('Async error');
}); }); ```_
Kundenspezifische Spiele¶
``javascript
// Custom matcher
expect.extend({
toBeWithinRange(received, floor, ceiling) {
const pass = received >= floor && received <= ceiling;
if (pass) {
return {
message: () =>expected ${received} not to be within range ${floor} - ${ceiling},
pass: true,
};
} else {
return {
message: () =>expected ${received} to be within range ${floor} - ${ceiling}`,
pass: false,
};
}
},
});
// Usage test('numeric ranges', () => { expect(100).toBeWithinRange(90, 110); expect(101).not.toBeWithinRange(0, 100); });
// Async custom matcher
expect.extend({
async toBeValidUser(received) {
const isValid = await validateUserAsync(received);
if (isValid) {
return {
message: () => expected ${received} not to be a valid user,
pass: true,
};
} else {
return {
message: () => expected ${received} to be a valid user,
pass: false,
};
}
},
});
```_
Async Testing¶
Verfahren¶
```javascript describe('Promise testing', () => { // Return promise test('async function returns promise', () => { return fetchUser(1).then(user => { expect(user.name).toBe('John'); }); });
// Async/await test('async function with async/await', async () => { const user = await fetchUser(1); expect(user.name).toBe('John'); });
// Testing rejections test('async function rejects', async () => { await expect(fetchUser(-1)).rejects.toThrow('User not found'); });
// Testing both resolve and reject test('promise resolves', () => { return expect(fetchUser(1)).resolves.toEqual({ id: 1, name: 'John' }); }); }); ```_
Rückrufe¶
```javascript describe('Callback testing', () => { // Callback with done test('callback function', (done) => { function callback(data) { try { expect(data).toBe('callback data'); done(); } catch (error) { done(error); } }
fetchDataCallback(callback);
});
// Testing error callbacks test('error callback', (done) => { function errorCallback(error) { try { expect(error.message).toBe('Something went wrong'); done(); } catch (err) { done(err); } }
fetchDataWithError(errorCallback);
}); }); ```_
Timer und Verzögerungen¶
```javascript describe('Timer testing', () => { beforeEach(() => { jest.useFakeTimers(); });
afterEach(() => { jest.useRealTimers(); });
test('delayed function', () => { const callback = jest.fn();
setTimeout(callback, 1000);
// Fast-forward time
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalled();
});
test('interval function', () => { const callback = jest.fn();
setInterval(callback, 1000);
// Fast-forward multiple intervals
jest.advanceTimersByTime(3000);
expect(callback).toHaveBeenCalledTimes(3);
});
test('run all timers', () => { const callback = jest.fn();
setTimeout(callback, 1000);
setTimeout(callback, 2000);
jest.runAllTimers();
expect(callback).toHaveBeenCalledTimes(2);
}); }); ```_
Mocking¶
Funktion Mocking¶
```javascript describe('Function mocking', () => { test('mock function', () => { const mockFn = jest.fn();
mockFn('arg1', 'arg2');
mockFn('arg3');
expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2');
expect(mockFn).toHaveBeenLastCalledWith('arg3');
});
test('mock return values', () => { const mockFn = jest.fn();
mockFn.mockReturnValue(42);
expect(mockFn()).toBe(42);
mockFn.mockReturnValueOnce(1).mockReturnValueOnce(2);
expect(mockFn()).toBe(1);
expect(mockFn()).toBe(2);
expect(mockFn()).toBe(42); // Default return value
});
test('mock implementation', () => { const mockFn = jest.fn((x, y) => x + y);
expect(mockFn(1, 2)).toBe(3);
expect(mockFn).toHaveBeenCalledWith(1, 2);
}); }); ```_
Modul Mocking¶
```javascript // userService.js import axios from 'axios';
export async function getUser(id) {
const response = await axios.get(/api/users/${id});
return response.data;
}
export async function createUser(userData) { const response = await axios.post('/api/users', userData); return response.data; }
// userService.test.js import axios from 'axios'; import { getUser, createUser } from './userService';
// Mock the entire axios module
jest.mock('axios');
const mockedAxios = axios as jest.Mocked
describe('User Service', () => { beforeEach(() => { jest.clearAllMocks(); });
test('should fetch user', async () => { const userData = { id: 1, name: 'John' }; mockedAxios.get.mockResolvedValue({ data: userData });
const user = await getUser(1);
expect(user).toEqual(userData);
expect(mockedAxios.get).toHaveBeenCalledWith('/api/users/1');
});
test('should create user', async () => { const newUser = { name: 'Jane', email: 'jane@example.com' }; const createdUser = { id: 2, ...newUser };
mockedAxios.post.mockResolvedValue({ data: createdUser });
const user = await createUser(newUser);
expect(user).toEqual(createdUser);
expect(mockedAxios.post).toHaveBeenCalledWith('/api/users', newUser);
}); }); ```_
Partial Mocking¶
```javascript // math.js export const add = (a, b) => a + b; export const subtract = (a, b) => a - b; export const multiply = (a, b) => a * b;
// calculator.js import * as math from './math';
export function calculate(operation, a, b) { switch (operation) { case 'add': return math.add(a, b); case 'subtract': return math.subtract(a, b); case 'multiply': return math.multiply(a, b); default: throw new Error('Unknown operation'); } }
// calculator.test.js import { calculate } from './calculator'; import * as math from './math';
// Partial mock - only mock specific functions jest.mock('./math', () => ({ ...jest.requireActual('./math'), add: jest.fn(), }));
const mockedMath = math as jest.Mocked
describe('Calculator', () => { test('should use mocked add function', () => { mockedMath.add.mockReturnValue(100);
const result = calculate('add', 2, 3);
expect(result).toBe(100);
expect(mockedMath.add).toHaveBeenCalledWith(2, 3);
});
test('should use real subtract function', () => { const result = calculate('subtract', 5, 3); expect(result).toBe(2); // Real implementation }); }); ```_
Spy Funktionen¶
```javascript describe('Spy functions', () => { test('spy on object method', () => { const user = { getName: () => 'John', setName: (name) => { this.name = name; } };
const spy = jest.spyOn(user, 'getName');
const name = user.getName();
expect(spy).toHaveBeenCalled();
expect(name).toBe('John');
spy.mockRestore(); // Restore original implementation
});
test('spy on console.log', () => { const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
console.log('Hello, World!');
expect(consoleSpy).toHaveBeenCalledWith('Hello, World!');
consoleSpy.mockRestore();
}); }); ```_
Mock Dateien¶
```javascript // mocks/fs.js const fs = jest.createMockFromModule('fs');
let mockFiles = Object.create(null);
function __setMockFiles(newMockFiles) { mockFiles = Object.create(null); for (const file in newMockFiles) { const dir = path.dirname(file);
if (!mockFiles[dir]) {
mockFiles[dir] = [];
}
mockFiles[dir].push(path.basename(file));
} }
function readFileSync(filePath) { return mockFiles[filePath] || ''; }
fs.__setMockFiles = __setMockFiles; fs.readFileSync = readFileSync;
module.exports = fs;
// fileReader.test.js import fs from 'fs'; import { readConfigFile } from './fileReader';
jest.mock('fs');
describe('File Reader', () => { test('should read config file', () => { fs.__setMockFiles({ '/config/app.json': '{"name": "MyApp"}' });
const config = readConfigFile('/config/app.json');
expect(config).toEqual({ name: 'MyApp' });
}); }); ```_
Snapshot Testing¶
Basic Snapshots¶
```javascript // Button.js import React from 'react';
export function Button({ children, onClick, variant = 'primary' }) { return ( ); }
// Button.test.js import React from 'react'; import { render } from '@testing-library/react'; import { Button } from './Button';
describe('Button', () => { test('should match snapshot', () => { const { container } = render( );
expect(container.firstChild).toMatchSnapshot();
});
test('should match snapshot with different variant', () => { const { container } = render( );
expect(container.firstChild).toMatchSnapshot();
}); }); ```_
Inline Snapshots¶
```javascript describe('Inline snapshots', () => { test('should match inline snapshot', () => { const user = { id: 1, name: 'John', createdAt: new Date('2023-01-01') };
expect(user).toMatchInlineSnapshot(`
Object {
"createdAt": 2023-01-01T00:00:00.000Z,
"id": 1,
"name": "John",
}
`);
}); }); ```_
Individuelle Snapshot Serializer¶
``javascript
// Custom serializer for Date objects
expect.addSnapshotSerializer({
test: (val) => val instanceof Date,
print: (val) =>Date("${val.toISOString()}")`,
});
// Custom serializer for React elements expect.addSnapshotSerializer({ test: (val) => val && val.$$typeof === Symbol.for('react.element'), print: (val, serialize) => { const { type, props } = val; const { children, ...restProps } = props;
return `<${type}${Object.keys(restProps).length ? ' ' + serialize(restProps) : ''}>${
children ? serialize(children) : ''
}</${type}>`;
}, }); ```_
Immobilien Matches¶
```javascript describe('Property matchers', () => { test('should match snapshot with dynamic values', () => { const user = { id: Math.random(), name: 'John', createdAt: new Date(), profile: { lastLogin: new Date(), sessionId: 'abc123' } };
expect(user).toMatchSnapshot({
id: expect.any(Number),
createdAt: expect.any(Date),
profile: {
lastLogin: expect.any(Date),
sessionId: expect.any(String)
}
});
}); }); ```_
Konfiguration¶
Jest Konfiguration Datei¶
```javascript // jest.config.js module.exports = { // Test environment testEnvironment: 'jsdom', // 'node' | 'jsdom'
// Test file patterns testMatch: [ '/tests//.(js|jsx|ts|tsx)', '**/.(test|spec).(js|jsx|ts|tsx)' ],
// Setup files
setupFilesAfterEnv: ['
// Module name mapping
moduleNameMapping: {
'^@/(.*)\(': '<rootDir>/src/\)1',
'\.(css|less|scss|sass)\(': 'identity-obj-proxy',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)\)': '
// Transform files
transform: {
'^.+\.(js|jsx|ts|tsx)\(': 'babel-jest',
'^.+\\.css\)': '
// Coverage collectCoverageFrom: [ 'src//*.{js,jsx,ts,tsx}', '!src//*.d.ts', '!src/index.js', '!src/serviceWorker.js' ],
coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80 } },
// Ignore patterns testPathIgnorePatterns: [ '/node_modules/', '/build/', '/dist/' ],
// Module directories
moduleDirectories: ['node_modules', '
// Globals globals: { 'ts-jest': { tsconfig: 'tsconfig.json' } } }; ```_
TypScript Konfiguration¶
```javascript // jest.config.js for TypeScript module.exports = { preset: 'ts-jest', testEnvironment: 'jsdom',
moduleNameMapping: { '^@/(.*)\(': '<rootDir>/src/\)1' },
setupFilesAfterEnv: ['
transform: { '^.+\.tsx?$': 'ts-jest' },
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
globals: { 'ts-jest': { tsconfig: { jsx: 'react-jsx' } } } }; ```_
Setup-Dateien¶
```javascript // src/setupTests.js import '@testing-library/jest-dom';
// Mock IntersectionObserver global.IntersectionObserver = class IntersectionObserver { constructor() {} disconnect() {} observe() {} unobserve() {} };
// Mock matchMedia Object.defineProperty(window, 'matchMedia', { writable: true, value: jest.fn().mockImplementation(query => ({ matches: false, media: query, onchange: null, addListener: jest.fn(), removeListener: jest.fn(), addEventListener: jest.fn(), removeEventListener: jest.fn(), dispatchEvent: jest.fn(), })), });
// Global test utilities global.testUtils = { createMockUser: () => ({ id: 1, name: 'Test User', email: 'test@example.com' }) }; ```_
Umweltvariablen¶
```javascript
// jest.config.js
module.exports = {
setupFiles: ['
// config/jest/setEnvVars.js process.env.NODE_ENV = 'test'; process.env.API_URL = 'http://localhost:3001'; process.env.FEATURE_FLAG_NEW_UI = 'true'; ```_
React Testing¶
Komponentenprüfung¶
```javascript // UserProfile.js import React, { useState, useEffect } from 'react';
export function UserProfile({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null);
useEffect(() => {
async function fetchUser() {
try {
const response = await fetch(/api/users/${userId});
if (!response.ok) {
throw new Error('User not found');
}
const userData = await response.json();
setUser(userData);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchUser();
}, [userId]);
if (loading) return
return (
{user.name}
Email: {user.email}
Age: {user.age}
// UserProfile.test.js import React from 'react'; import { render, screen, waitFor } from '@testing-library/react'; import { UserProfile } from './UserProfile';
// Mock fetch global.fetch = jest.fn();
describe('UserProfile', () => { beforeEach(() => { fetch.mockClear(); });
test('should display loading state', () => { fetch.mockImplementation(() => new Promise(() => {})); // Never resolves
render(<UserProfile userId={1} />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
test('should display user data', async () => { const mockUser = { id: 1, name: 'John Doe', email: 'john@example.com', age: 30 };
fetch.mockResolvedValueOnce({
ok: true,
json: async () => mockUser
});
render(<UserProfile userId={1} />);
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
expect(screen.getByText('Email: john@example.com')).toBeInTheDocument();
expect(screen.getByText('Age: 30')).toBeInTheDocument();
});
test('should display error message', async () => { fetch.mockRejectedValueOnce(new Error('Network error'));
render(<UserProfile userId={1} />);
await waitFor(() => {
expect(screen.getByText('Error: Network error')).toBeInTheDocument();
});
}); }); ```_
Veranstaltungsprüfung¶
```javascript // Counter.js import React, { useState } from 'react';
export function Counter({ initialValue = 0, onCountChange }) { const [count, setCount] = useState(initialValue);
const increment = () => { const newCount = count + 1; setCount(newCount); onCountChange?.(newCount); };
const decrement = () => { const newCount = count - 1; setCount(newCount); onCountChange?.(newCount); };
const reset = () => { setCount(initialValue); onCountChange?.(initialValue); };
return (
// Counter.test.js import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Counter } from './Counter';
describe('Counter', () => {
test('should increment count', async () => {
const user = userEvent.setup();
render(
const incrementButton = screen.getByText('+');
await user.click(incrementButton);
expect(screen.getByTestId('count')).toHaveTextContent('1');
});
test('should call onCountChange when count changes', async () => { const user = userEvent.setup(); const onCountChange = jest.fn();
render(<Counter onCountChange={onCountChange} />);
const incrementButton = screen.getByText('+');
await user.click(incrementButton);
expect(onCountChange).toHaveBeenCalledWith(1);
});
test('should reset to initial value', async () => {
const user = userEvent.setup();
render(
const incrementButton = screen.getByText('+');
const resetButton = screen.getByText('Reset');
await user.click(incrementButton);
expect(screen.getByTestId('count')).toHaveTextContent('6');
await user.click(resetButton);
expect(screen.getByTestId('count')).toHaveTextContent('5');
}); }); ```_
Hook Testing¶
```javascript // useCounter.js import { useState, useCallback } from 'react';
export function useCounter(initialValue = 0) { const [count, setCount] = useState(initialValue);
const increment = useCallback(() => { setCount(prev => prev + 1); }, []);
const decrement = useCallback(() => { setCount(prev => prev - 1); }, []);
const reset = useCallback(() => { setCount(initialValue); }, [initialValue]);
return { count, increment, decrement, reset }; }
// useCounter.test.js import { renderHook, act } from '@testing-library/react'; import { useCounter } from './useCounter';
describe('useCounter', () => { test('should initialize with default value', () => { const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
});
test('should initialize with custom value', () => { const { result } = renderHook(() => useCounter(10));
expect(result.current.count).toBe(10);
});
test('should increment count', () => { const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
test('should decrement count', () => { const { result } = renderHook(() => useCounter(5));
act(() => {
result.current.decrement();
});
expect(result.current.count).toBe(4);
});
test('should reset count', () => { const { result } = renderHook(() => useCounter(10));
act(() => {
result.current.increment();
result.current.increment();
});
expect(result.current.count).toBe(12);
act(() => {
result.current.reset();
});
expect(result.current.count).toBe(10);
}); }); ```_
Code Coverage¶
Coverage Konfiguration¶
```javascript // jest.config.js module.exports = { collectCoverage: true,
collectCoverageFrom: [ 'src//*.{js,jsx,ts,tsx}', '!src//.d.ts', '!src/index.js', '!src/reportWebVitals.js', '!src/**/.stories.{js,jsx,ts,tsx}', '!src/**/*.test.{js,jsx,ts,tsx}' ],
coverageDirectory: 'coverage',
coverageReporters: [ 'text', 'lcov', 'html', 'json' ],
coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80 }, './src/components/': { branches: 90, functions: 90, lines: 90, statements: 90 } } }; ```_
Coverage Scripts¶
json
{
"scripts": {
"test:coverage": "jest --coverage",
"test:coverage:watch": "jest --coverage --watchAll",
"test:coverage:ci": "jest --coverage --ci --watchAll=false",
"coverage:open": "open coverage/lcov-report/index.html"
}
}_
Ignorieren von Coverage¶
```javascript // Ignore specific lines function myFunction() { /* istanbul ignore next */ if (process.env.NODE_ENV === 'development') { console.log('Development mode'); }
return 'result'; }
// Ignore entire function /* istanbul ignore next */ function debugFunction() { console.log('Debug info'); }
// Ignore else branch function processValue(value) { if (value) { return value.toUpperCase(); } /* istanbul ignore else */ else { return ''; } } ```_
Erweiterte Funktionen¶
Benutzerdefinierte Testumgebung¶
```javascript // jest-environment-custom.js const { TestEnvironment } = require('jest-environment-jsdom');
class CustomTestEnvironment extends TestEnvironment { constructor(config, context) { super(config, context);
// Add custom globals
this.global.customGlobal = 'test-value';
}
async setup() { await super.setup();
// Custom setup logic
this.global.mockDatabase = {
users: [],
addUser: (user) => this.global.mockDatabase.users.push(user),
getUser: (id) => this.global.mockDatabase.users.find(u => u.id === id)
};
}
async teardown() { // Custom teardown logic this.global.mockDatabase = null;
await super.teardown();
} }
module.exports = CustomTestEnvironment;
// jest.config.js module.exports = { testEnvironment: './jest-environment-custom.js' }; ```_
Kundenspezifische Reporter¶
```javascript // custom-reporter.js class CustomReporter { constructor(globalConfig, options) { this._globalConfig = globalConfig; this._options = options; }
onRunStart(results, options) { console.log('🚀 Starting test run...'); }
onTestStart(test) {
console.log(▶️ Running ${test.path});
}
onTestResult(test, testResult, aggregatedResult) {
if (testResult.numFailingTests > 0) {
console.log(❌ ${test.path} - ${testResult.numFailingTests} failed);
} else {
console.log(✅ ${test.path} - all tests passed);
}
}
onRunComplete(contexts, results) {
console.log(🏁 Test run complete: ${results.numPassedTests} passed, ${results.numFailedTests} failed);
}
}
module.exports = CustomReporter;
// jest.config.js module.exports = { reporters: [ 'default', ['./custom-reporter.js', { option1: 'value1' }] ] }; ```_
Global Setup und Teardown¶
```javascript // jest.config.js module.exports = { globalSetup: './config/jest/globalSetup.js', globalTeardown: './config/jest/globalTeardown.js' };
// config/jest/globalSetup.js module.exports = async () => { console.log('🔧 Global setup');
// Start test database global.TEST_DB = await startTestDatabase();
// Start mock server global.MOCK_SERVER = await startMockServer(); };
// config/jest/globalTeardown.js module.exports = async () => { console.log('🧹 Global teardown');
// Stop test database await global.TEST_DB.stop();
// Stop mock server await global.MOCK_SERVER.stop(); }; ```_
Sehen Sie Plugins¶
```javascript // watch-plugin-custom.js class CustomWatchPlugin { constructor({ stdin, stdout, config, testPathPattern }) { this._stdin = stdin; this._stdout = stdout; this._config = config; this._testPathPattern = testPathPattern; }
apply(jestHooks) { jestHooks.onFileChange(({ projects }) => { console.log('📁 Files changed, running tests...'); }); }
getUsageInfo() { return { key: 'c', prompt: 'clear console' }; }
run(globalConfig, updateConfigAndRun) { console.clear(); return Promise.resolve(); } }
module.exports = CustomWatchPlugin;
// jest.config.js module.exports = { watchPlugins: [ 'jest-watch-typeahead/filename', 'jest-watch-typeahead/testname', './watch-plugin-custom.js' ] }; ```_
Prüfmuster¶
Seite Objektmodell¶
```javascript // pageObjects/LoginPage.js export class LoginPage { constructor(page) { this.page = page; }
get emailInput() { return this.page.getByLabelText(/email/i); }
get passwordInput() { return this.page.getByLabelText(/password/i); }
get submitButton() { return this.page.getByRole('button', { name: /login/i }); }
get errorMessage() { return this.page.getByTestId('error-message'); }
async login(email, password) { await this.emailInput.fill(email); await this.passwordInput.fill(password); await this.submitButton.click(); }
async expectErrorMessage(message) { await expect(this.errorMessage).toHaveText(message); } }
// Login.test.js import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { LoginPage } from './pageObjects/LoginPage'; import { Login } from './Login';
describe('Login', () => {
test('should display error for invalid credentials', async () => {
const user = userEvent.setup();
render(
const loginPage = new LoginPage(screen);
await loginPage.login('invalid@email.com', 'wrongpassword');
await loginPage.expectErrorMessage('Invalid credentials');
}); }); ```_
Testfaktoren¶
```javascript // factories/userFactory.js export const createUser = (overrides = {}) => ({ id: Math.floor(Math.random() * 1000), name: 'John Doe', email: 'john@example.com', age: 30, isActive: true, createdAt: new Date(), ...overrides });
export const createUsers = (count, overrides = {}) => Array.from({ length: count }, (_, index) => createUser({ id: index + 1, ...overrides }) );
// Usage in tests import { createUser, createUsers } from './factories/userFactory';
describe('User Service', () => { test('should process user data', () => { const user = createUser({ name: 'Jane Doe', age: 25 });
const result = processUser(user);
expect(result.displayName).toBe('Jane Doe (25)');
});
test('should handle multiple users', () => { const users = createUsers(5, { isActive: true });
const activeUsers = filterActiveUsers(users);
expect(activeUsers).toHaveLength(5);
}); }); ```_
Test Utilities¶
```javascript // testUtils/renderWithProviders.js import React from 'react'; import { render } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from 'react-query'; import { ThemeProvider } from 'styled-components'; import { theme } from '../src/theme';
export function renderWithProviders(
ui,
{
initialEntries = ['/'],
queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false }
}
}),
...renderOptions
} = {}
) {
function Wrapper({ children }) {
return (
return render(ui, { wrapper: Wrapper, ...renderOptions }); }
// Usage import { renderWithProviders } from './testUtils/renderWithProviders';
test('should render with all providers', () => {
renderWithProviders(
Benutzerdefinierte Spiele für Domain Logic¶
```javascript // matchers/userMatchers.js expect.extend({ toBeValidUser(received) { const pass = received && typeof received.id === 'number' && typeof received.name === 'string' && received.name.length > 0 && typeof received.email === 'string' && received.email.includes('@');
if (pass) {
return {
message: () => `expected ${JSON.stringify(received)} not to be a valid user`,
pass: true,
};
} else {
return {
message: () => `expected ${JSON.stringify(received)} to be a valid user`,
pass: false,
};
}
},
toHavePermission(received, permission) { const pass = received.permissions && received.permissions.includes(permission);
if (pass) {
return {
message: () => `expected user not to have permission "${permission}"`,
pass: true,
};
} else {
return {
message: () => `expected user to have permission "${permission}"`,
pass: false,
};
}
} });
// Usage test('should validate user object', () => { const user = { id: 1, name: 'John', email: 'john@example.com' }; expect(user).toBeValidUser(); });
test('should check user permissions', () => { const user = { permissions: ['read', 'write'] }; expect(user).toHavePermission('read'); }); ```_
Leistung¶
Optimierung der Testleistung¶
```javascript // jest.config.js module.exports = { // Parallel execution maxWorkers: '50%', // Use 50% of available cores
// Cache cache: true, cacheDirectory: '/tmp/jest_cache',
// Faster test discovery testPathIgnorePatterns: [ '/node_modules/', '/build/', '/coverage/' ],
// Optimize transforms transform: { '^.+\.(js|jsx)$': ['babel-jest', { cacheDirectory: true }] },
// Reduce overhead clearMocks: true, restoreMocks: true,
// Faster test environment testEnvironment: 'jsdom', testEnvironmentOptions: { url: 'http://localhost' } }; ```_
Selektive Testlauf¶
```bash
Run only changed files¶
npm test -- --onlyChanged
Run related tests¶
npm test -- --findRelatedTests src/components/Button.js
Run tests matching pattern¶
npm test -- --testNamePattern="should render"
Run specific test suites¶
npm test -- --testPathPattern="components"
Bail on first failure¶
npm test -- --bail
Run tests in band (no parallel)¶
npm test -- --runInBand ```_
Speicheroptimierung¶
```javascript // jest.config.js module.exports = { // Limit memory usage workerIdleMemoryLimit: '512MB',
// Clear modules between tests clearMocks: true, resetMocks: true, restoreMocks: true,
// Optimize setup
setupFilesAfterEnv: ['
// Reduce test overhead testEnvironment: 'jsdom',
// Optimize coverage collection collectCoverageFrom: [ 'src//*.{js,jsx}', '!src//.test.{js,jsx}', '!src/**/.stories.{js,jsx}' ] };
// Cleanup in tests afterEach(() => { jest.clearAllMocks(); cleanup(); // From @testing-library/react }); ```_
Integration¶
CI/CD Integration¶
```yaml
.github/workflows/test.yml¶
name: Tests
on: [push, pull_request]
jobs: test: runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test -- --ci --coverage --watchAll=false
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
file: ./coverage/lcov.info
```_
Docker Integration¶
```dockerfile
Dockerfile.test¶
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./ RUN npm ci
COPY . .
CMD ["npm", "test", "--", "--ci", "--coverage", "--watchAll=false"] ```_
IDE Integration¶
json
// .vscode/settings.json
{
"jest.jestCommandLine": "npm test --",
"jest.autoRun": {
"watch": true,
"onStartup": ["all-tests"]
},
"jest.showCoverageOnLoad": true,
"jest.coverageFormatter": "DefaultFormatter",
"jest.debugMode": true
}_
Fehlerbehebung¶
Gemeinsame Themen¶
```bash
Clear Jest cache¶
npx jest --clearCache
Debug Jest configuration¶
npx jest --showConfig
Run with debug output¶
DEBUG=jest* npm test
Memory issues¶
NODE_OPTIONS="--max-old-space-size=4096" npm test
Permission issues¶
sudo chown -R $(whoami) node_modules/.cache ```_
Debugging Tests¶
```javascript // Debug specific test test.only('debug this test', () => { console.log('Debug info'); debugger; // Use with --inspect-brk expect(true).toBe(true); });
// Debug with VS Code // Add to launch.json { "type": "node", "request": "launch", "name": "Jest Debug", "program": "${workspaceFolder}/node_modules/.bin/jest", "args": ["--runInBand", "--no-cache"], "console": "integratedTerminal", "internalConsoleOptions": "neverOpen" } ```_
Mock Issues¶
```javascript // Reset mocks between tests beforeEach(() => { jest.clearAllMocks(); jest.resetAllMocks(); jest.restoreAllMocks(); });
// Mock implementation issues const mockFn = jest.fn(); mockFn.mockImplementation(() => { throw new Error('Mock error'); });
// Module mock issues jest.mock('./module', () => ({ __esModule: true, default: jest.fn(), namedExport: jest.fn() })); ```_
Best Practices¶
Prüforganisation¶
- ** beschreibende Namen*: Klare, beschreibende Testnamen verwenden
- **Arrange-Act-Assert*: Folgen Sie dem AAA-Muster
- **Single Verantwortung*: Eine Behauptung pro Test, wenn möglich
- Teststruktur: Gruppenbezogene Tests mit
describeBlöcken
Prüfqualität¶
- **Edge Cases*: Prüfgrenzbedingungen und Randfälle
- **Error Handling*: Testfehlerszenarien und Ausnahmen
- **Async Code*: Asynchrone Operationen richtig testen
- **Mocking Strategie*: Fehlt externe Abhängigkeiten entsprechend
Leistung¶
- Selective Running: Uhrmodus und selektiver Testlauf
- **Parallel Execution*: Leverage Jests parallele Ausführung
- **Cache Optimization*: Verwenden Sie Jests Caching effektiv
- Memory Management: Ressourcen und Mocks reinigen
Wartung¶
- Regular Updates: Halten Sie Jest und testen Sie Bibliotheken aktualisiert
- **Coverage Goals*: Setzen und pflegen Sie entsprechende Deckungsschwellen
- Testdokumentation: Dokumentenkomplexe Testszenarien
- **Refactoring*: Refactor-Tests zusammen mit Produktionscode
--
Zusammenfassung¶
Jest ist ein umfassendes JavaScript-Test-Framework, das bietet:
- **Zero Konfiguration*: Arbeiten aus der Box für die meisten Projekte
- Kraftspieler: Umfangreiche Durchsetzungsbibliothek mit benutzerdefiniertem Matcher-Support
- **Mocking Capabilities*: Eingebautes Mocking für Funktionen, Module und Timer
- **Snapshot Testing*: Visuelle Regressionsprüfung für UI-Komponenten
- Code Coverage: Eingebaute Deckungsberichterstattung mit Schwellwertdurchsetzung
- **Parallel Execution*: Schnelle Testausführung mit Arbeiterprozessen
- Watch Mode: Interaktive Entwicklung mit automatischer Test-Rerunning
- **Erweitertes Ökosystem*: Reiches Ökosystem von Plugins und Integrationen
Jest zeichnet sich durch React-Anwendungen, Node.js Backends und allgemeinen JavaScript-Code aus. Sein Fokus auf Entwicklererfahrung, umfassendes Feature-Set und exzellente Dokumentation machen es zur Wahl für JavaScript-Tests in modernen Entwicklungs-Workflows.
<= <= <= <================================================================================= Funktion copyToClipboard() {\cHFFFF} const commands = document.querySelectorAll('code'); alle Befehle = ''; Befehle. Für jede(cmd) => alle Befehle += cmd.textContent + '\n'); navigator.clipboard.writeText (allCommands); Alarm ('Alle Befehle, die in die Zwischenablage kopiert werden!'); }
Funktion generierenPDF() { Fenster.print(); }