Zum Inhalt

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>

generieren

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
my-project/ ├── src/ │ ├── components/ │ │ ├── Button.js │ │ └── Button.test.js │ ├── utils/ │ │ ├── math.js │ │ └── math.test.js │ └── tests/ │ └── integration.test.js ├── mocks/ │ └── fileMock.js ├── jest.config.js └── package.json ```_

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: ['/src/setupTests.js'],

// 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)\)': '/mocks/fileMock.js' },

// Transform files transform: { '^.+\.(js|jsx|ts|tsx)\(': 'babel-jest', '^.+\\.css\)': '/config/jest/cssTransform.js' },

// 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', '/src'],

// 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: ['/src/setupTests.ts'],

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'], };

// 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

Loading...
; if (error) return
Error: {error}
; if (!user) return
No user found
;

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 (

{count}
); }

// 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 ( {children} ); }

return render(ui, { wrapper: Wrapper, ...renderOptions }); }

// Usage import { renderWithProviders } from './testUtils/renderWithProviders';

test('should render with all providers', () => { renderWithProviders(); // Test implementation }); ```_

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: ['/src/setupTests.js'],

// 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 describe Blö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(); }