🎯 Exemplos recomendados
Balanced sample collections from various categories for you to explore
Exemplos Vitest
Exemplos de framework de testing JavaScript moderno com integração Vite, featuring unit tests, component testing, e patterns de testing modernos
💻 Configuração Básica Vitest typescript
🟢 simple
⭐⭐
Configuração completa do Vitest e configuração de testing básica com suporte TypeScript
⏱️ 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"]
}
}
💻 Testing Unitário Vitest typescript
🟡 intermediate
⭐⭐⭐
Exemplos comprehensivos de testing unitário com assertions, mocks, e testing assíncrono
⏱️ 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)
})
})
💻 Testing de Componentes Vitest typescript
🟡 intermediate
⭐⭐⭐
Exemplos de testing de componentes React e Vue com Vitest e 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)
})
})
💻 Patterns Avançados de Testing Vitest typescript
🔴 complex
⭐⭐⭐⭐
Patterns de testing avançados incluindo integration tests, performance tests, e utilitários de testing
⏱️ 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')
})
})