Vitest Samples

Modern JavaScript testing framework examples with Vite integration, featuring unit tests, component testing, and modern testing patterns

💻 Vitest Basic Setup typescript

🟢 simple ⭐⭐

Complete Vitest configuration and basic testing setup with TypeScript support

⏱️ 15 min 🏷️ vitest, testing, setup
Prerequisites: Node.js, npm/pnpm, Basic testing concepts
// Vitest Basic Setup Example

// 1. Installation
// npm install -D vitest jsdom @testing-library/vue @testing-library/react
// or: pnpm add -D vitest jsdom @testing-library/vue @testing-library/react

// 2. vitest.config.ts - Configuration file
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    vue(), // For Vue projects
    react(), // For React projects
  ],
  test: {
    // Global test environment
    environment: 'jsdom', // or 'node' for backend testing

    // Setup files to run before tests
    setupFiles: ['./src/test/setup.ts'],

    // Coverage configuration
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json', 'html'],
      exclude: [
        'node_modules/',
        'src/test/',
        '**/*.d.ts',
        '**/*.config.*'
      ]
    },

    // Test matching patterns
    include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
    exclude: ['node_modules/', 'dist/', '.idea/', '.git/', '.cache/'],

    // Watch mode settings
    watch: false, // Set true for development

    // Global variables available in tests
    globals: true,

    // Timeout for each test (ms)
    testTimeout: 10000,

    // Hook timeout (ms)
    hookTimeout: 10000
  },

  // Regular Vite configuration
  resolve: {
    alias: {
      '@': '/src'
    }
  }
})

// 3. src/test/setup.ts - Test setup file
import { vi } from 'vitest'

// Mock global APIs
Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: vi.fn().mockImplementation(query => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: vi.fn(), // deprecated
    removeListener: vi.fn(), // deprecated
    addEventListener: vi.fn(),
    removeEventListener: vi.fn(),
    dispatchEvent: vi.fn(),
  })),
})

// Mock localStorage
const localStorageMock = {
  getItem: vi.fn(),
  setItem: vi.fn(),
  removeItem: vi.fn(),
  clear: vi.fn(),
}
vi.stubGlobal('localStorage', localStorageMock)

// Mock fetch
global.fetch = vi.fn()

// 4. Package.json scripts
{
  "scripts": {
    "test": "vitest",
    "test:ui": "vitest --ui",
    "test:run": "vitest run",
    "test:coverage": "vitest run --coverage",
    "test:watch": "vitest --watch"
  }
}

// 5. tsconfig.json - Add to compilerOptions
{
  "compilerOptions": {
    "types": ["vitest/globals"]
  }
}

💻 Vitest Unit Testing typescript

🟡 intermediate ⭐⭐⭐

Comprehensive unit testing examples with assertions, mocks, and async testing

⏱️ 25 min 🏷️ vitest, unit tests, mocks
Prerequisites: Basic Vitest setup, JavaScript/TypeScript, Testing concepts
// Vitest Unit Testing Examples

// 1. src/calculator.ts - Code to test
export class Calculator {
  add(a: number, b: number): number {
    return a + b
  }

  subtract(a: number, b: number): number {
    return a - b
  }

  multiply(a: number, b: number): number {
    return a * b
  }

  divide(a: number, b: number): number {
    if (b === 0) {
      throw new Error('Division by zero is not allowed')
    }
    return a / b
  }

  async asyncAdd(a: number, b: number): Promise<number> {
    return new Promise((resolve) => {
      setTimeout(() => resolve(a + b), 100)
    })
  }

  factorial(n: number): number {
    if (n < 0) throw new Error('Factorial of negative number')
    if (n === 0) return 1
    return n * this.factorial(n - 1)
  }
}

// 2. src/calculator.test.ts - Test file
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { Calculator } from './calculator'

describe('Calculator', () => {
  let calculator: Calculator

  beforeEach(() => {
    calculator = new Calculator()
  })

  afterEach(() => {
    vi.clearAllMocks()
  })

  describe('Basic arithmetic operations', () => {
    it('should add two numbers correctly', () => {
      expect(calculator.add(2, 3)).toBe(5)
      expect(calculator.add(-1, 1)).toBe(0)
      expect(calculator.add(0, 0)).toBe(0)
    })

    it('should subtract two numbers correctly', () => {
      expect(calculator.subtract(5, 3)).toBe(2)
      expect(calculator.subtract(0, 5)).toBe(-5)
      expect(calculator.subtract(-2, -3)).toBe(1)
    })

    it('should multiply two numbers correctly', () => {
      expect(calculator.multiply(3, 4)).toBe(12)
      expect(calculator.multiply(-2, 3)).toBe(-6)
      expect(calculator.multiply(0, 5)).toBe(0)
    })

    it('should divide two numbers correctly', () => {
      expect(calculator.divide(10, 2)).toBe(5)
      expect(calculator.divide(-6, 3)).toBe(-2)
      expect(calculator.divide(7, 2)).toBe(3.5)
    })

    it('should throw error when dividing by zero', () => {
      expect(() => calculator.divide(5, 0)).toThrow('Division by zero is not allowed')
    })
  })

  describe('Edge cases and special values', () => {
    it('should handle decimal numbers', () => {
      expect(calculator.add(2.5, 1.5)).toBeCloseTo(4.0)
      expect(calculator.multiply(2.5, 2)).toBe(5.0)
    })

    it('should handle very large numbers', () => {
      const largeNumber = Number.MAX_SAFE_INTEGER
      expect(calculator.add(largeNumber, 1)).toBe(largeNumber + 1)
    })
  })

  describe('Async operations', () => {
    it('should add numbers asynchronously', async () => {
      const result = await calculator.asyncAdd(2, 3)
      expect(result).toBe(5)
    })

    it('should handle async operations with timeout', async () => {
      const startTime = Date.now()
      await calculator.asyncAdd(1, 2)
      const endTime = Date.now()

      expect(endTime - startTime).toBeGreaterThanOrEqual(100)
    })
  })

  describe('Recursive operations', () => {
    it('should calculate factorial correctly', () => {
      expect(calculator.factorial(0)).toBe(1)
      expect(calculator.factorial(1)).toBe(1)
      expect(calculator.factorial(5)).toBe(120)
      expect(calculator.factorial(10)).toBe(3628800)
    })

    it('should throw error for negative factorial', () => {
      expect(() => calculator.factorial(-1)).toThrow('Factorial of negative number')
    })
  })
})

// 3. Advanced assertions examples
import { describe, it, expect } from 'vitest'

describe('Advanced Assertions', () => {
  it('demonstrates various matchers', () => {
    const user = {
      id: 1,
      name: 'John Doe',
      email: '[email protected]',
      preferences: {
        theme: 'dark',
        notifications: true
      },
      tags: ['developer', 'javascript']
    }

    // Basic equality
    expect(user.name).toBe('John Doe')

    // Object containing
    expect(user).toEqual(
      expect.objectContaining({
        name: 'John Doe',
        email: '[email protected]'
      })
    )

    // Array containing
    expect(user.tags).toEqual(
      expect.arrayContaining(['developer'])
    )

    // String matching
    expect(user.email).toMatch(/@example\.com$/)
    expect(user.email).toContain('@')

    // Number comparisons
    expect(user.id).toBeGreaterThan(0)
    expect(user.id).toBeLessThan(100)

    // Truthiness
    expect(user.preferences.notifications).toBeTruthy()
    expect(user.preferences.theme).toBeDefined()

    // Type checking
    expect(typeof user.name).toBe('string')
    expect(Array.isArray(user.tags)).toBe(true)

    // Custom matchers
    expect(user.tags.length).toBe(2)
  })

  it('demonstrates negated matchers', () => {
    const value = 'hello'

    expect(value).not.toBe('world')
    expect(value).not.toMatch(/[0-9]/)
    expect(value).not.toBeNull()
    expect(value).not.toBeUndefined()
  })
})

// 4. Mock functions examples
import { describe, it, expect, vi } from 'vitest'

describe('Mock Functions', () => {
  it('should create and track mock function calls', () => {
    const mockFn = vi.fn()

    mockFn()
    mockFn('hello')
    mockFn('world', 123)

    // Call counts
    expect(mockFn).toHaveBeenCalledTimes(3)

    // Call arguments
    expect(mockFn).toHaveBeenNthCalledWith(1)
    expect(mockFn).toHaveBeenNthCalledWith(2, 'hello')
    expect(mockFn).toHaveBeenNthCalledWith(3, 'world', 123)
  })

  it('should use mock return values', () => {
    const mockFn = vi.fn()
      .mockReturnValueOnce(42)
      .mockReturnValueOnce(100)
      .mockReturnValue('default')

    expect(mockFn()).toBe(42)
    expect(mockFn()).toBe(100)
    expect(mockFn()).toBe('default')
    expect(mockFn()).toBe('default')
  })

  it('should use mock resolved values', async () => {
    const asyncMock = vi.fn()
      .mockResolvedValueOnce('first result')
      .mockResolvedValueOnce('second result')
      .mockRejectedValueOnce(new Error('Async error'))

    expect(await asyncMock()).toBe('first result')
    expect(await asyncMock()).toBe('second result')
    await expect(asyncMock()).rejects.toThrow('Async error')
  })

  it('should mock implementation', () => {
    const addMock = vi.fn((a: number, b: number) => a + b)

    expect(addMock(2, 3)).toBe(5)
    expect(addMock).toHaveBeenCalledWith(2, 3)
    expect(addMock).toHaveBeenCalledTimes(1)
  })
})

💻 Vitest Component Testing typescript

🟡 intermediate ⭐⭐⭐

React and Vue component testing examples with Vitest and Testing Library

⏱️ 30 min 🏷️ vitest, component testing, react, vue
Prerequisites: React/Vue basics, Vitest setup, Testing Library
// Vitest Component Testing Examples

// 1. React Component Testing Example
// src/components/Counter.tsx
import React, { useState } from 'react'

interface CounterProps {
  initialValue?: number
  onCountChange?: (count: number) => void
}

export const Counter: React.FC<CounterProps> = ({
  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 = () => {
    const newCount = initialValue
    setCount(newCount)
    onCountChange?.(newCount)
  }

  return (
    <div className="counter">
      <h2>Counter: {count}</h2>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  )
}

// src/components/Counter.test.tsx
import { describe, it, expect, vi } from 'vitest'
import { render, screen, fireEvent } from '@testing-library/react'
import { Counter } from './Counter'

describe('Counter Component', () => {
  it('renders with initial value', () => {
    render(<Counter initialValue={5} />)

    expect(screen.getByText('Counter: 5')).toBeInTheDocument()
    expect(screen.getByText('+')).toBeInTheDocument()
    expect(screen.getByText('-')).toBeInTheDocument()
    expect(screen.getByText('Reset')).toBeInTheDocument()
  })

  it('increments count when + button is clicked', () => {
    render(<Counter initialValue={0} />)

    const incrementButton = screen.getByText('+')
    fireEvent.click(incrementButton)

    expect(screen.getByText('Counter: 1')).toBeInTheDocument()
  })

  it('decrements count when - button is clicked', () => {
    render(<Counter initialValue={10} />)

    const decrementButton = screen.getByText('-')
    fireEvent.click(decrementButton)

    expect(screen.getByText('Counter: 9')).toBeInTheDocument()
  })

  it('resets count when reset button is clicked', () => {
    render(<Counter initialValue={5} />)

    // First increment
    fireEvent.click(screen.getByText('+'))
    expect(screen.getByText('Counter: 6')).toBeInTheDocument()

    // Then reset
    fireEvent.click(screen.getByText('Reset'))
    expect(screen.getByText('Counter: 5')).toBeInTheDocument()
  })

  it('calls onCountChange callback when count changes', () => {
    const mockOnChange = vi.fn()
    render(<Counter initialValue={0} onCountChange={mockOnChange} />)

    fireEvent.click(screen.getByText('+'))
    expect(mockOnChange).toHaveBeenCalledWith(1)

    fireEvent.click(screen.getByText('-'))
    expect(mockOnChange).toHaveBeenCalledWith(-1)

    fireEvent.click(screen.getByText('Reset'))
    expect(mockOnChange).toHaveBeenCalledWith(0)
  })
})

// 2. Vue Component Testing Example
// src/components/UserProfile.vue
<template>
  <div class="user-profile">
    <div v-if="loading" class="loading">Loading...</div>
    <div v-else-if="error" class="error">{{ error }}</div>
    <div v-else class="profile">
      <img :src="user.avatar" :alt="user.name" class="avatar" />
      <h2>{{ user.name }}</h2>
      <p>{{ user.email }}</p>
      <div class="stats">
        <span>Followers: {{ user.followers }}</span>
        <span>Following: {{ user.following }}</span>
      </div>
      <button @click="toggleFollow">
        {{ isFollowing ? 'Unfollow' : 'Follow' }}
      </button>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'

interface User {
  id: number
  name: string
  email: string
  avatar: string
  followers: number
  following: number
}

interface Props {
  userId: number
}

const props = defineProps<Props>()
const emit = defineEmits<{
  follow: [{ userId: number, isFollowing: boolean }]
}>()

const loading = ref(true)
const error = ref<string | null>(null)
const user = ref<User | null>(null)
const isFollowing = ref(false)

const followCount = computed(() => {
  return user.value ? user.value.followers + (isFollowing.value ? 1 : 0) : 0
})

const loadUser = async () => {
  try {
    loading.value = true
    error.value = null

    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 1000))

    // Mock data
    user.value = {
      id: props.userId,
      name: `User ${props.userId}`,
      email: `user${props.userId}@example.com`,
      avatar: `https://i.pravatar.cc/150?img=${props.userId}`,
      followers: 100,
      following: 50
    }
  } catch (err) {
    error.value = err instanceof Error ? err.message : 'Failed to load user'
  } finally {
    loading.value = false
  }
}

const toggleFollow = () => {
  if (user.value) {
    isFollowing.value = !isFollowing.value
    emit('follow', {
      userId: user.value.id,
      isFollowing: isFollowing.value
    })
  }
}

loadUser()
</script>

// src/components/UserProfile.test.ts
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { nextTick } from 'vue'
import UserProfile from './UserProfile.vue'

describe('UserProfile Component', () => {
  it('shows loading state initially', () => {
    const wrapper = mount(UserProfile, {
      props: { userId: 1 }
    })

    expect(wrapper.find('.loading').exists()).toBe(true)
    expect(wrapper.text()).toContain('Loading...')
  })

  it('displays user profile after loading', async () => {
    const wrapper = mount(UserProfile, {
      props: { userId: 1 }
    })

    // Wait for async data loading
    await new Promise(resolve => setTimeout(resolve, 1100))
    await nextTick()

    expect(wrapper.find('.loading').exists()).toBe(false)
    expect(wrapper.find('.profile').exists()).toBe(true)
    expect(wrapper.text()).toContain('User 1')
    expect(wrapper.text()).toContain('[email protected]')
  })

  it('shows follow button and handles clicks', async () => {
    const wrapper = mount(UserProfile, {
      props: { userId: 1 }
    })

    await new Promise(resolve => setTimeout(resolve, 1100))
    await nextTick()

    const followButton = wrapper.find('button')
    expect(followButton.exists()).toBe(true)
    expect(followButton.text()).toBe('Follow')

    await followButton.trigger('click')
    expect(followButton.text()).toBe('Unfollow')

    const emitted = wrapper.emitted('follow')
    expect(emitted).toBeDefined()
    expect(emitted![0]).toEqual([{ userId: 1, isFollowing: true }])
  })

  it('calculates follower count correctly', async () => {
    const wrapper = mount(UserProfile, {
      props: { userId: 1 }
    })

    await new Promise(resolve => setTimeout(resolve, 1100))
    await nextTick()

    expect(wrapper.text()).toContain('Followers: 100')

    await wrapper.find('button').trigger('click')
    await nextTick()

    expect(wrapper.text()).toContain('Followers: 101')
  })
})

// 3. Custom Hooks Testing Example
// src/hooks/useCounter.ts
import { ref, computed } from 'vue'

export function useCounter(initialValue: number = 0) {
  const count = ref(initialValue)

  const increment = () => {
    count.value += 1
  }

  const decrement = () => {
    count.value -= 1
  }

  const reset = () => {
    count.value = initialValue
  }

  const setValue = (value: number) => {
    count.value = value
  }

  const isEven = computed(() => count.value % 2 === 0)
  const isPositive = computed(() => count.value > 0)

  return {
    count: readonly(count),
    increment,
    decrement,
    reset,
    setValue,
    isEven,
    isPositive
  }
}

// src/hooks/useCounter.test.ts
import { describe, it, expect } from 'vitest'
import { useCounter } from './useCounter'
import { ref } from 'vue'

describe('useCounter Hook', () => {
  it('initializes with default value', () => {
    const { count, isEven, isPositive } = useCounter()

    expect(count.value).toBe(0)
    expect(isEven.value).toBe(true)
    expect(isPositive.value).toBe(false)
  })

  it('initializes with custom value', () => {
    const { count } = useCounter(5)

    expect(count.value).toBe(5)
  })

  it('increments count correctly', () => {
    const { count, increment } = useCounter(2)

    increment()
    expect(count.value).toBe(3)

    increment()
    expect(count.value).toBe(4)
  })

  it('decrements count correctly', () => {
    const { count, decrement } = useCounter(5)

    decrement()
    expect(count.value).toBe(4)

    decrement()
    expect(count.value).toBe(3)
  })

  it('resets count to initial value', () => {
    const { count, increment, reset } = useCounter(10)

    increment()
    increment()
    expect(count.value).toBe(12)

    reset()
    expect(count.value).toBe(10)
  })

  it('sets count to specific value', () => {
    const { count, setValue } = useCounter()

    setValue(100)
    expect(count.value).toBe(100)
  })

  it('computes even/odd correctly', () => {
    const { count, increment, isEven } = useCounter(0)

    expect(isEven.value).toBe(true)

    increment()
    expect(isEven.value).toBe(false)

    increment()
    expect(isEven.value).toBe(true)
  })
})

💻 Vitest Advanced Testing Patterns typescript

🔴 complex ⭐⭐⭐⭐

Advanced testing patterns including integration tests, performance tests, and test utilities

⏱️ 40 min 🏷️ vitest, integration, performance, advanced
Prerequisites: Advanced Vitest, TypeScript, Testing patterns
// Vitest Advanced Testing Patterns

// 1. Integration Testing Example
// src/services/userService.ts
export interface User {
  id: number
  name: string
  email: string
  role: 'user' | 'admin'
}

export interface UserRepository {
  findById(id: number): Promise<User | null>
  create(userData: Omit<User, 'id'>): Promise<User>
  update(id: number, userData: Partial<User>): Promise<User>
  delete(id: number): Promise<boolean>
}

export class UserService {
  constructor(private repository: UserRepository) {}

  async getUser(id: number): Promise<User | null> {
    const user = await this.repository.findById(id)
    if (!user) {
      throw new Error(`User with id ${id} not found`)
    }
    return user
  }

  async createUser(userData: Omit<User, 'id'>): Promise<User> {
    if (!userData.email?.includes('@')) {
      throw new Error('Invalid email format')
    }
    return this.repository.create(userData)
  }

  async updateUserRole(id: number, role: User['role']): Promise<User> {
    const user = await this.getUser(id)
    return this.repository.update(id, { role })
  }

  async deleteUser(id: number): Promise<boolean> {
    await this.getUser(id) // Will throw if user doesn't exist
    return this.repository.delete(id)
  }
}

// src/services/userService.integration.test.ts
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { UserService, User, UserRepository } from './userService'

// Mock repository for testing
class MockUserRepository implements UserRepository {
  private users: User[] = []
  private nextId = 1

  async findById(id: number): Promise<User | null> {
    return this.users.find(user => user.id === id) || null
  }

  async create(userData: Omit<User, 'id'>): Promise<User> {
    const user: User = {
      id: this.nextId++,
      ...userData
    }
    this.users.push(user)
    return user
  }

  async update(id: number, userData: Partial<User>): Promise<User> {
    const userIndex = this.users.findIndex(user => user.id === id)
    if (userIndex === -1) {
      throw new Error(`User with id ${id} not found`)
    }
    this.users[userIndex] = { ...this.users[userIndex], ...userData }
    return this.users[userIndex]
  }

  async delete(id: number): Promise<boolean> {
    const userIndex = this.users.findIndex(user => user.id === id)
    if (userIndex === -1) return false
    this.users.splice(userIndex, 1)
    return true
  }

  clear() {
    this.users = []
    this.nextId = 1
  }
}

describe('UserService Integration Tests', () => {
  let userService: UserService
  let mockRepository: MockUserRepository

  beforeEach(() => {
    mockRepository = new MockUserRepository()
    userService = new UserService(mockRepository)
    mockRepository.clear()
  })

  describe('User CRUD operations', () => {
    it('should create and retrieve a user', async () => {
      const userData = {
        name: 'John Doe',
        email: '[email protected]',
        role: 'user' as const
      }

      const createdUser = await userService.createUser(userData)
      expect(createdUser.id).toBe(1)
      expect(createdUser.name).toBe('John Doe')

      const retrievedUser = await userService.getUser(createdUser.id)
      expect(retrievedUser).toEqual(createdUser)
    })

    it('should throw error for invalid email when creating user', async () => {
      const userData = {
        name: 'John Doe',
        email: 'invalid-email',
        role: 'user' as const
      }

      await expect(userService.createUser(userData)).rejects.toThrow('Invalid email format')
    })

    it('should update user role', async () => {
      const userData = {
        name: 'John Doe',
        email: '[email protected]',
        role: 'user' as const
      }

      const user = await userService.createUser(userData)
      const updatedUser = await userService.updateUserRole(user.id, 'admin')

      expect(updatedUser.role).toBe('admin')
    })

    it('should delete user', async () => {
      const userData = {
        name: 'John Doe',
        email: '[email protected]',
        role: 'user' as const
      }

      const user = await userService.createUser(userData)
      const deleted = await userService.deleteUser(user.id)

      expect(deleted).toBe(true)
      await expect(userService.getUser(user.id)).rejects.toThrow('not found')
    })
  })
})

// 2. Performance Testing Example
// src/utils/performance.ts
export function fibonacci(n: number): number {
  if (n <= 1) return n
  return fibonacci(n - 1) + fibonacci(n - 2)
}

export function memoizedFibonacci(): (n: number) => number {
  const cache = new Map<number, number>()

  return function fib(n: number): number {
    if (cache.has(n)) return cache.get(n)!
    if (n <= 1) return n

    const result = fib(n - 1) + fib(n - 2)
    cache.set(n, result)
    return result
  }
}

export async function processLargeArray<T>(array: T[], processor: (item: T) => Promise<T>): Promise<T[]> {
  const promises = array.map(processor)
  return Promise.all(promises)
}

// src/utils/performance.test.ts
import { describe, it, expect } from 'vitest'
import { fibonacci, memoizedFibonacci, processLargeArray } from './performance'

describe('Performance Tests', () => {
  it('should complete fibonacci calculation within reasonable time', () => {
    const start = performance.now()
    const result = fibonacci(20)
    const end = performance.now()

    expect(result).toBe(6765)
    expect(end - start).toBeLessThan(1000) // Should complete within 1 second
  })

  it('should show memoized version is significantly faster', () => {
    const memFib = memoizedFibonacci()
    const n = 40

    // Non-memoized version
    const start1 = performance.now()
    const result1 = fibonacci(n)
    const end1 = performance.now()
    const time1 = end1 - start1

    // Memoized version
    const start2 = performance.now()
    const result2 = memFib(n)
    const end2 = performance.now()
    const time2 = end2 - start2

    expect(result1).toBe(result2)
    expect(time2).toBeLessThan(time1 / 10) // Memoized should be at least 10x faster
  })

  it('should process large array efficiently', async () => {
    const largeArray = Array.from({ length: 1000 }, (_, i) => i)

    const processor = async (item: number): Promise<number> => {
      await new Promise(resolve => setTimeout(resolve, 1))
      return item * 2
    }

    const start = performance.now()
    const result = await processLargeArray(largeArray, processor)
    const end = performance.now()

    expect(result).toHaveLength(1000)
    expect(result[0]).toBe(0)
    expect(result[999]).toBe(1998)
    expect(end - start).toBeLessThan(5000) // Should complete within 5 seconds
  })
})

// 3. Test Utilities and Fixtures
// src/test/utils/testUtils.ts
import { render, RenderOptions } from '@testing-library/react'
import { ReactElement } from 'react'

// Custom render function with providers
export const renderWithProviders = (
  ui: ReactElement,
  options: RenderOptions = {}
) => {
  // Add any global providers here (Theme, Router, etc.)
  return render(ui, {
    ...options,
  })
}

// Test data factory
export class UserFactory {
  static create(overrides: Partial<User> = {}): User {
    return {
      id: Math.floor(Math.random() * 1000),
      name: 'Test User',
      email: '[email protected]',
      role: 'user',
      ...overrides
    }
  }

  static createMany(count: number, overrides: Partial<User> = {}): User[] {
    return Array.from({ length: count }, () => this.create(overrides))
  }
}

// Mock responses
export const createMockApiResponse = <T>(data: T, status = 200) => ({
  data,
  status,
  headers: { 'content-type': 'application/json' }
})

// 4. API Testing Example
// src/services/api.ts
export interface ApiResponse<T> {
  data: T
  status: number
  message: string
}

export class ApiClient {
  constructor(private baseUrl: string) {}

  async get<T>(endpoint: string): Promise<ApiResponse<T>> {
    const response = await fetch(`${this.baseUrl}${endpoint}`)
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }
    const data = await response.json()
    return {
      data,
      status: response.status,
      message: 'Success'
    }
  }

  async post<T>(endpoint: string, body: any): Promise<ApiResponse<T>> {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body)
    })

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`)
    }

    const data = await response.json()
    return {
      data,
      status: response.status,
      message: 'Created'
    }
  }
}

// src/services/api.test.ts
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { ApiClient } from './api'

describe('ApiClient', () => {
  let apiClient: ApiClient
  let mockFetch: ReturnType<typeof vi.fn>

  beforeEach(() => {
    mockFetch = vi.fn()
    global.fetch = mockFetch
    apiClient = new ApiClient('https://api.example.com')
  })

  it('should make GET request and return response', async () => {
    const mockData = { id: 1, name: 'Test' }
    mockFetch.mockResolvedValueOnce({
      ok: true,
      status: 200,
      json: async () => mockData
    })

    const result = await apiClient.get('/users/1')

    expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/users/1')
    expect(result.data).toEqual(mockData)
    expect(result.status).toBe(200)
    expect(result.message).toBe('Success')
  })

  it('should handle HTTP errors', async () => {
    mockFetch.mockResolvedValueOnce({
      ok: false,
      status: 404
    })

    await expect(apiClient.get('/users/999')).rejects.toThrow('HTTP error! status: 404')
  })

  it('should make POST request with body', async () => {
    const requestBody = { name: 'New User' }
    const responseData = { id: 2, ...requestBody }

    mockFetch.mockResolvedValueOnce({
      ok: true,
      status: 201,
      json: async () => responseData
    })

    const result = await apiClient.post('/users', requestBody)

    expect(mockFetch).toHaveBeenCalledWith(
      'https://api.example.com/users',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(requestBody)
      }
    )
    expect(result.data).toEqual(responseData)
    expect(result.status).toBe(201)
    expect(result.message).toBe('Created')
  })
})