לדלג לתוכן

10.6 Vitest פתרון

פתרון - קונספט בדיקות ו-Vitest - Testing Concepts and Vitest

פתרון תרגיל 1

// utils/string.ts
export function capitalize(str: string): string {
  if (!str) return '';
  return str.charAt(0).toUpperCase() + str.slice(1);
}

export function slugify(str: string): string {
  return str
    .toLowerCase()
    .trim()
    .replace(/[^\w\s-]/g, '')
    .replace(/[\s_]+/g, '-')
    .replace(/^-+|-+$/g, '');
}

export function truncate(str: string, maxLength: number): string {
  if (!str) return '';
  if (str.length <= maxLength) return str;
  return str.slice(0, maxLength - 3) + '...';
}

export function countWords(str: string): number {
  if (!str || !str.trim()) return 0;
  return str.trim().split(/\s+/).length;
}
// utils/string.test.ts
import { describe, it, expect } from 'vitest';
import { capitalize, slugify, truncate, countWords } from './string';

describe('capitalize', () => {
  it('should capitalize the first letter', () => {
    expect(capitalize('hello')).toBe('Hello');
  });

  it('should handle already capitalized string', () => {
    expect(capitalize('Hello')).toBe('Hello');
  });

  it('should handle empty string', () => {
    expect(capitalize('')).toBe('');
  });

  it('should handle single character', () => {
    expect(capitalize('a')).toBe('A');
  });

  it('should not change other characters', () => {
    expect(capitalize('hELLO wORLD')).toBe('HELLO wORLD');
  });
});

describe('slugify', () => {
  it('should convert spaces to hyphens', () => {
    expect(slugify('Hello World')).toBe('hello-world');
  });

  it('should convert to lowercase', () => {
    expect(slugify('My Blog Post')).toBe('my-blog-post');
  });

  it('should remove special characters', () => {
    expect(slugify('Hello! World?')).toBe('hello-world');
  });

  it('should handle multiple spaces', () => {
    expect(slugify('hello   world')).toBe('hello-world');
  });

  it('should trim leading and trailing hyphens', () => {
    expect(slugify(' -hello world- ')).toBe('hello-world');
  });

  it('should handle empty string', () => {
    expect(slugify('')).toBe('');
  });
});

describe('truncate', () => {
  it('should not truncate short strings', () => {
    expect(truncate('hello', 10)).toBe('hello');
  });

  it('should truncate and add ellipsis', () => {
    expect(truncate('Hello World, this is a long text', 15)).toBe('Hello World,...');
  });

  it('should handle exact length', () => {
    expect(truncate('hello', 5)).toBe('hello');
  });

  it('should handle empty string', () => {
    expect(truncate('', 10)).toBe('');
  });

  it('should handle maxLength less than 3', () => {
    expect(truncate('hello', 3)).toBe('...');
  });
});

describe('countWords', () => {
  it('should count words in a sentence', () => {
    expect(countWords('hello world')).toBe(2);
  });

  it('should handle multiple spaces', () => {
    expect(countWords('hello   world   test')).toBe(3);
  });

  it('should return 0 for empty string', () => {
    expect(countWords('')).toBe(0);
  });

  it('should return 0 for whitespace only', () => {
    expect(countWords('   ')).toBe(0);
  });

  it('should count single word', () => {
    expect(countWords('hello')).toBe(1);
  });
});

פתרון תרגיל 2

// utils/array.ts
export function unique<T>(arr: T[]): T[] {
  return [...new Set(arr)];
}

export function groupBy<T>(arr: T[], key: keyof T): Record<string, T[]> {
  return arr.reduce((groups, item) => {
    const groupKey = String(item[key]);
    if (!groups[groupKey]) groups[groupKey] = [];
    groups[groupKey].push(item);
    return groups;
  }, {} as Record<string, T[]>);
}

export function sortBy<T>(arr: T[], ...keys: (keyof T)[]): T[] {
  return [...arr].sort((a, b) => {
    for (const key of keys) {
      if (a[key] < b[key]) return -1;
      if (a[key] > b[key]) return 1;
    }
    return 0;
  });
}

export function flatten<T>(arr: (T | T[])[]): T[] {
  return arr.flat() as T[];
}

export function chunk<T>(arr: T[], size: number): T[][] {
  if (size <= 0) throw new Error('Chunk size must be positive');
  const result: T[][] = [];
  for (let i = 0; i < arr.length; i += size) {
    result.push(arr.slice(i, i + size));
  }
  return result;
}
// utils/array.test.ts
import { describe, it, expect } from 'vitest';
import { unique, groupBy, sortBy, flatten, chunk } from './array';

describe('unique', () => {
  it('should remove duplicates from numbers', () => {
    expect(unique([1, 2, 2, 3, 3, 3])).toEqual([1, 2, 3]);
  });

  it('should remove duplicates from strings', () => {
    expect(unique(['a', 'b', 'a'])).toEqual(['a', 'b']);
  });

  it('should handle empty array', () => {
    expect(unique([])).toEqual([]);
  });
});

describe('groupBy', () => {
  const items = [
    { name: 'A', category: 'x' },
    { name: 'B', category: 'y' },
    { name: 'C', category: 'x' },
  ];

  it('should group items by key', () => {
    const result = groupBy(items, 'category');
    expect(result['x']).toHaveLength(2);
    expect(result['y']).toHaveLength(1);
  });

  it('should handle empty array', () => {
    expect(groupBy([], 'category' as any)).toEqual({});
  });

  it('should include all items in groups', () => {
    const result = groupBy(items, 'category');
    const totalItems = Object.values(result).flat().length;
    expect(totalItems).toBe(items.length);
  });
});

describe('sortBy', () => {
  const items = [
    { name: 'C', age: 30 },
    { name: 'A', age: 25 },
    { name: 'B', age: 25 },
  ];

  it('should sort by single key', () => {
    const result = sortBy(items, 'name');
    expect(result[0].name).toBe('A');
    expect(result[2].name).toBe('C');
  });

  it('should sort by multiple keys', () => {
    const result = sortBy(items, 'age', 'name');
    expect(result[0].name).toBe('A');
    expect(result[1].name).toBe('B');
  });

  it('should not mutate original array', () => {
    const original = [...items];
    sortBy(items, 'name');
    expect(items).toEqual(original);
  });
});

describe('flatten', () => {
  it('should flatten nested arrays', () => {
    expect(flatten([1, [2, 3], 4, [5]])).toEqual([1, 2, 3, 4, 5]);
  });

  it('should handle already flat array', () => {
    expect(flatten([1, 2, 3])).toEqual([1, 2, 3]);
  });

  it('should handle empty array', () => {
    expect(flatten([])).toEqual([]);
  });
});

describe('chunk', () => {
  it('should split array into chunks', () => {
    expect(chunk([1, 2, 3, 4, 5], 2)).toEqual([[1, 2], [3, 4], [5]]);
  });

  it('should handle exact division', () => {
    expect(chunk([1, 2, 3, 4], 2)).toEqual([[1, 2], [3, 4]]);
  });

  it('should handle size larger than array', () => {
    expect(chunk([1, 2], 5)).toEqual([[1, 2]]);
  });

  it('should throw for non-positive size', () => {
    expect(() => chunk([1, 2], 0)).toThrow();
  });
});

פתרון תרגיל 3

// cart.test.ts - בדיקות נכתבו קודם (TDD)
import { describe, it, expect, beforeEach } from 'vitest';
import { createCart, Cart } from './cart';

describe('Cart', () => {
  let cart: Cart;

  beforeEach(() => {
    cart = createCart();
  });

  describe('addItem', () => {
    it('should add a new item', () => {
      cart.addItem({ id: '1', name: 'מוצר א', price: 100 });
      expect(cart.items).toHaveLength(1);
      expect(cart.items[0].quantity).toBe(1);
    });

    it('should increase quantity for existing item', () => {
      cart.addItem({ id: '1', name: 'מוצר א', price: 100 });
      cart.addItem({ id: '1', name: 'מוצר א', price: 100 });
      expect(cart.items).toHaveLength(1);
      expect(cart.items[0].quantity).toBe(2);
    });

    it('should add with custom quantity', () => {
      cart.addItem({ id: '1', name: 'מוצר א', price: 100 }, 5);
      expect(cart.items[0].quantity).toBe(5);
    });
  });

  describe('removeItem', () => {
    it('should remove an existing item', () => {
      cart.addItem({ id: '1', name: 'מוצר א', price: 100 });
      cart.removeItem('1');
      expect(cart.items).toHaveLength(0);
    });

    it('should not throw when removing non-existing item', () => {
      expect(() => cart.removeItem('999')).not.toThrow();
    });
  });

  describe('updateQuantity', () => {
    it('should update quantity', () => {
      cart.addItem({ id: '1', name: 'מוצר א', price: 100 });
      cart.updateQuantity('1', 5);
      expect(cart.items[0].quantity).toBe(5);
    });

    it('should remove item when quantity is 0', () => {
      cart.addItem({ id: '1', name: 'מוצר א', price: 100 });
      cart.updateQuantity('1', 0);
      expect(cart.items).toHaveLength(0);
    });

    it('should not allow negative quantity', () => {
      cart.addItem({ id: '1', name: 'מוצר א', price: 100 });
      expect(() => cart.updateQuantity('1', -1)).toThrow();
    });
  });

  describe('getTotal', () => {
    it('should return 0 for empty cart', () => {
      expect(cart.getTotal()).toBe(0);
    });

    it('should calculate total correctly', () => {
      cart.addItem({ id: '1', name: 'מוצר א', price: 100 }, 2);
      cart.addItem({ id: '2', name: 'מוצר ב', price: 50 }, 3);
      expect(cart.getTotal()).toBe(350); // 200 + 150
    });
  });

  describe('getItemCount', () => {
    it('should return total item count', () => {
      cart.addItem({ id: '1', name: 'מוצר א', price: 100 }, 2);
      cart.addItem({ id: '2', name: 'מוצר ב', price: 50 }, 3);
      expect(cart.getItemCount()).toBe(5);
    });
  });

  describe('clear', () => {
    it('should remove all items', () => {
      cart.addItem({ id: '1', name: 'מוצר א', price: 100 });
      cart.addItem({ id: '2', name: 'מוצר ב', price: 50 });
      cart.clear();
      expect(cart.items).toHaveLength(0);
      expect(cart.getTotal()).toBe(0);
    });
  });

  describe('getItem', () => {
    it('should return item by id', () => {
      cart.addItem({ id: '1', name: 'מוצר א', price: 100 });
      expect(cart.getItem('1')?.name).toBe('מוצר א');
    });

    it('should return undefined for non-existing item', () => {
      expect(cart.getItem('999')).toBeUndefined();
    });
  });
});
// cart.ts - קוד שנכתב אחרי הבדיקות
interface CartItem {
  id: string;
  name: string;
  price: number;
  quantity: number;
}

export interface Cart {
  items: CartItem[];
  addItem(item: Omit<CartItem, 'quantity'>, quantity?: number): void;
  removeItem(id: string): void;
  updateQuantity(id: string, quantity: number): void;
  getTotal(): number;
  getItemCount(): number;
  clear(): void;
  getItem(id: string): CartItem | undefined;
}

export function createCart(): Cart {
  const items: CartItem[] = [];

  return {
    get items() {
      return [...items];
    },

    addItem(item, quantity = 1) {
      const existing = items.find((i) => i.id === item.id);
      if (existing) {
        existing.quantity += quantity;
      } else {
        items.push({ ...item, quantity });
      }
    },

    removeItem(id) {
      const index = items.findIndex((i) => i.id === id);
      if (index !== -1) items.splice(index, 1);
    },

    updateQuantity(id, quantity) {
      if (quantity < 0) throw new Error('Quantity cannot be negative');
      if (quantity === 0) {
        this.removeItem(id);
        return;
      }
      const item = items.find((i) => i.id === id);
      if (item) item.quantity = quantity;
    },

    getTotal() {
      return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
    },

    getItemCount() {
      return items.reduce((sum, item) => sum + item.quantity, 0);
    },

    clear() {
      items.length = 0;
    },

    getItem(id) {
      return items.find((i) => i.id === id);
    },
  };
}

פתרון תרגיל 4

// utils/api.test.ts
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { fetchUser, fetchWithRetry, debounce } from './api';

// Mock של fetch
const mockFetch = vi.fn();
global.fetch = mockFetch;

describe('fetchUser', () => {
  afterEach(() => {
    vi.restoreAllMocks();
  });

  it('should return user data on success', async () => {
    const userData = { id: 1, name: 'דני' };
    mockFetch.mockResolvedValue({
      ok: true,
      json: () => Promise.resolve(userData),
    });

    const result = await fetchUser(1);
    expect(result).toEqual(userData);
    expect(mockFetch).toHaveBeenCalledWith('/api/users/1');
  });

  it('should throw on 404', async () => {
    mockFetch.mockResolvedValue({ ok: false, status: 404 });

    await expect(fetchUser(999)).rejects.toThrow('User 999 not found');
  });

  it('should throw on network error', async () => {
    mockFetch.mockRejectedValue(new Error('Network error'));

    await expect(fetchUser(1)).rejects.toThrow('Network error');
  });
});

describe('fetchWithRetry', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.useRealTimers();
    vi.restoreAllMocks();
  });

  it('should succeed on first try', async () => {
    mockFetch.mockResolvedValue({ ok: true });

    const result = await fetchWithRetry('/api/data');
    expect(result.ok).toBe(true);
    expect(mockFetch).toHaveBeenCalledTimes(1);
  });

  it('should retry and succeed', async () => {
    mockFetch
      .mockRejectedValueOnce(new Error('fail'))
      .mockResolvedValueOnce({ ok: true });

    const promise = fetchWithRetry('/api/data', 3, 100);
    await vi.advanceTimersByTimeAsync(100);
    const result = await promise;

    expect(result.ok).toBe(true);
    expect(mockFetch).toHaveBeenCalledTimes(2);
  });

  it('should throw after all retries fail', async () => {
    mockFetch.mockRejectedValue(new Error('fail'));

    const promise = fetchWithRetry('/api/data', 3, 100);
    await vi.advanceTimersByTimeAsync(300);

    await expect(promise).rejects.toThrow('fail');
    expect(mockFetch).toHaveBeenCalledTimes(3);
  });
});

describe('debounce', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it('should call function after delay', () => {
    const fn = vi.fn();
    const debouncedFn = debounce(fn, 300);

    debouncedFn();
    expect(fn).not.toHaveBeenCalled();

    vi.advanceTimersByTime(300);
    expect(fn).toHaveBeenCalledTimes(1);
  });

  it('should only call once for multiple rapid calls', () => {
    const fn = vi.fn();
    const debouncedFn = debounce(fn, 300);

    debouncedFn();
    debouncedFn();
    debouncedFn();

    vi.advanceTimersByTime(300);
    expect(fn).toHaveBeenCalledTimes(1);
  });

  it('should pass arguments to the function', () => {
    const fn = vi.fn();
    const debouncedFn = debounce(fn, 300);

    debouncedFn('hello', 42);
    vi.advanceTimersByTime(300);

    expect(fn).toHaveBeenCalledWith('hello', 42);
  });

  it('should reset timer on new call', () => {
    const fn = vi.fn();
    const debouncedFn = debounce(fn, 300);

    debouncedFn();
    vi.advanceTimersByTime(200);
    debouncedFn(); // מתחיל מחדש
    vi.advanceTimersByTime(200);

    expect(fn).not.toHaveBeenCalled();

    vi.advanceTimersByTime(100);
    expect(fn).toHaveBeenCalledTimes(1);
  });
});

פתרון תרגיל 5

// utils/validation.ts
interface RegistrationForm {
  username: string;
  email: string;
  password: string;
  confirmPassword: string;
  age: number;
  agreeToTerms: boolean;
}

interface ValidationResult {
  valid: boolean;
  errors: Record<string, string>;
}

export function validateRegistration(form: RegistrationForm): ValidationResult {
  const errors: Record<string, string> = {};

  // username
  if (!form.username) {
    errors.username = 'Username is required';
  } else if (form.username.length < 3 || form.username.length > 20) {
    errors.username = 'Username must be 3-20 characters';
  } else if (!/^[a-zA-Z0-9_]+$/.test(form.username)) {
    errors.username = 'Username can only contain letters, numbers, and underscores';
  }

  // email
  if (!form.email) {
    errors.email = 'Email is required';
  } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) {
    errors.email = 'Invalid email format';
  }

  // password
  if (!form.password) {
    errors.password = 'Password is required';
  } else if (form.password.length < 8) {
    errors.password = 'Password must be at least 8 characters';
  } else if (!/[A-Z]/.test(form.password) || !/[a-z]/.test(form.password) || !/[0-9]/.test(form.password)) {
    errors.password = 'Password must contain uppercase, lowercase, and number';
  }

  // confirmPassword
  if (form.password !== form.confirmPassword) {
    errors.confirmPassword = 'Passwords do not match';
  }

  // age
  if (form.age < 13 || form.age > 120) {
    errors.age = 'Age must be between 13 and 120';
  }

  // agreeToTerms
  if (!form.agreeToTerms) {
    errors.agreeToTerms = 'You must agree to the terms';
  }

  return { valid: Object.keys(errors).length === 0, errors };
}
// utils/validation.test.ts
import { describe, it, expect } from 'vitest';
import { validateRegistration } from './validation';

const validForm = {
  username: 'john_doe',
  email: 'john@example.com',
  password: 'MyPass123',
  confirmPassword: 'MyPass123',
  age: 25,
  agreeToTerms: true,
};

describe('validateRegistration', () => {
  it('should pass with valid form', () => {
    const result = validateRegistration(validForm);
    expect(result.valid).toBe(true);
    expect(Object.keys(result.errors)).toHaveLength(0);
  });

  describe('username validation', () => {
    it('should reject empty username', () => {
      const result = validateRegistration({ ...validForm, username: '' });
      expect(result.errors.username).toBeDefined();
    });

    it('should reject short username', () => {
      const result = validateRegistration({ ...validForm, username: 'ab' });
      expect(result.errors.username).toContain('3-20');
    });

    it('should reject special characters', () => {
      const result = validateRegistration({ ...validForm, username: 'user@name' });
      expect(result.errors.username).toBeDefined();
    });

    it('should accept valid username with underscore', () => {
      const result = validateRegistration({ ...validForm, username: 'user_123' });
      expect(result.errors.username).toBeUndefined();
    });
  });

  describe('email validation', () => {
    it('should reject empty email', () => {
      const result = validateRegistration({ ...validForm, email: '' });
      expect(result.errors.email).toBeDefined();
    });

    it('should reject invalid email format', () => {
      const result = validateRegistration({ ...validForm, email: 'notanemail' });
      expect(result.errors.email).toBeDefined();
    });

    it('should accept valid email', () => {
      const result = validateRegistration({ ...validForm, email: 'user@domain.co.il' });
      expect(result.errors.email).toBeUndefined();
    });
  });

  describe('password validation', () => {
    it('should reject short password', () => {
      const form = { ...validForm, password: 'Ab1', confirmPassword: 'Ab1' };
      const result = validateRegistration(form);
      expect(result.errors.password).toBeDefined();
    });

    it('should reject password without uppercase', () => {
      const form = { ...validForm, password: 'mypass123', confirmPassword: 'mypass123' };
      const result = validateRegistration(form);
      expect(result.errors.password).toBeDefined();
    });

    it('should reject mismatched passwords', () => {
      const form = { ...validForm, confirmPassword: 'DifferentPass1' };
      const result = validateRegistration(form);
      expect(result.errors.confirmPassword).toBeDefined();
    });
  });

  describe('age validation', () => {
    it('should reject age under 13', () => {
      const result = validateRegistration({ ...validForm, age: 10 });
      expect(result.errors.age).toBeDefined();
    });

    it('should reject age over 120', () => {
      const result = validateRegistration({ ...validForm, age: 150 });
      expect(result.errors.age).toBeDefined();
    });

    it('should accept age of 13', () => {
      const result = validateRegistration({ ...validForm, age: 13 });
      expect(result.errors.age).toBeUndefined();
    });
  });

  describe('terms agreement', () => {
    it('should reject when terms not agreed', () => {
      const result = validateRegistration({ ...validForm, agreeToTerms: false });
      expect(result.errors.agreeToTerms).toBeDefined();
    });
  });

  it('should return multiple errors', () => {
    const form = {
      username: '',
      email: '',
      password: '',
      confirmPassword: 'x',
      age: 5,
      agreeToTerms: false,
    };
    const result = validateRegistration(form);
    expect(result.valid).toBe(false);
    expect(Object.keys(result.errors).length).toBeGreaterThan(3);
  });
});

פתרון תרגיל 6

// storage.test.ts
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { Storage } from './storage';

describe('Storage', () => {
  let storage: Storage;

  beforeEach(() => {
    localStorage.clear();
    storage = new Storage('test');
  });

  afterEach(() => {
    localStorage.clear();
  });

  describe('set and get', () => {
    it('should store and retrieve a string', () => {
      storage.set('name', 'דני');
      expect(storage.get('name')).toBe('דני');
    });

    it('should store and retrieve a number', () => {
      storage.set('age', 25);
      expect(storage.get('age')).toBe(25);
    });

    it('should store and retrieve an object', () => {
      const user = { name: 'דני', age: 25 };
      storage.set('user', user);
      expect(storage.get('user')).toEqual(user);
    });

    it('should store and retrieve an array', () => {
      storage.set('items', [1, 2, 3]);
      expect(storage.get('items')).toEqual([1, 2, 3]);
    });

    it('should return default value for missing key', () => {
      expect(storage.get('missing', 'default')).toBe('default');
    });

    it('should return undefined for missing key without default', () => {
      expect(storage.get('missing')).toBeUndefined();
    });
  });

  describe('prefix', () => {
    it('should use prefix in storage key', () => {
      storage.set('key', 'value');
      expect(localStorage.getItem('test:key')).toBe('"value"');
    });

    it('should not interfere with other prefixes', () => {
      const otherStorage = new Storage('other');
      storage.set('key', 'value1');
      otherStorage.set('key', 'value2');

      expect(storage.get('key')).toBe('value1');
      expect(otherStorage.get('key')).toBe('value2');
    });
  });

  describe('remove', () => {
    it('should remove an item', () => {
      storage.set('key', 'value');
      storage.remove('key');
      expect(storage.get('key')).toBeUndefined();
    });

    it('should not throw when removing non-existing key', () => {
      expect(() => storage.remove('missing')).not.toThrow();
    });
  });

  describe('clear', () => {
    it('should clear only prefixed items', () => {
      storage.set('key1', 'value1');
      storage.set('key2', 'value2');
      localStorage.setItem('other:key', 'should remain');

      storage.clear();

      expect(storage.get('key1')).toBeUndefined();
      expect(storage.get('key2')).toBeUndefined();
      expect(localStorage.getItem('other:key')).toBe('should remain');
    });
  });

  describe('has', () => {
    it('should return true for existing key', () => {
      storage.set('key', 'value');
      expect(storage.has('key')).toBe(true);
    });

    it('should return false for missing key', () => {
      expect(storage.has('missing')).toBe(false);
    });
  });
});

תשובות לשאלות

1. בדיקת יחידה בודקת פונקציה בודדת בבידוד - למשל בדיקה שפונקציית validateEmail מחזירה true/false נכון. בדיקת אינטגרציה בודקת שמספר חלקים עובדים יחד - למשל בדיקה שקומפוננטת טופס מציגה שגיאות validation כשהמשתמש מגיש טופס לא תקין (שילוב של DOM, event handlers, ו-validation logic).

2. TDD: (1) אדום - כותבים בדיקה שנכשלת כי הקוד עוד לא קיים. (2) ירוק - כותבים את הקוד המינימלי שגורם לבדיקה לעבור. (3) שיפור - משפרים את הקוד בלי לשבור את הבדיקות. היתרון: הבדיקה מגדירה את הדרישה לפני הקוד, מה שמבטיח שכל קוד שנכתב באמת נבדק ושהממשק מעוצב מנקודת המבט של המשתמש בקוד.

3. toBe משתמש ב-=== (שוויון זהות) - מתאים לערכים פרימיטיביים (מספרים, מחרוזות, boolean). toEqual עושה השוואה עמוקה - מתאים לאובייקטים ומערכים. toBe({ a: 1 }) ייכשל כי שני אובייקטים שונים בזיכרון, אבל toEqual({ a: 1 }) יצליח.

4. בלי fake timers, בדיקות שמשתמשות ב-setTimeout או setInterval צריכות לחכות בזמן אמת (למשל 3 שניות). זה הופך את הבדיקות לאיטיות ולא אמינות. fake timers מאפשרים "לקפוץ קדימה" בזמן באופן מיידי, מה שהופך את הבדיקות למהירות ודטרמיניסטיות.

5. beforeEach רץ לפני כל בדיקה בודדת - מתאים לאתחול state שצריך להיות נקי לכל בדיקה (למשל ניקוי מסד נתונים). beforeAll רץ פעם אחת לפני כל הבדיקות בבלוק - מתאים לפעולות יקרות שלא צריך לחזור עליהן (למשל חיבור למסד נתונים).