Documentation Index
Fetch the complete documentation index at: https://mintlify.com/visible/cruel/llms.txt
Use this file to discover all available pages before exploring further.
Testing Integration
Integrate chaos engineering into your test suite with Bun, Vitest, or Jest.Bun Test Integration
Basic setup with Bun:import { cruel } from 'cruel'
import { describe, test, beforeEach, expect } from 'bun:test'
beforeEach(() => {
cruel.reset()
})
describe('API Client', () => {
async function fetchUser(id: string) {
const response = await fetch(`https://api.example.com/users/${id}`)
return response.json()
}
test('handles network latency', async () => {
const slow = cruel.network.latency(fetchUser, 1000)
const start = Date.now()
await slow('123')
const elapsed = Date.now() - start
expect(elapsed).toBeGreaterThanOrEqual(1000)
})
test('retries on failure', async () => {
let attempts = 0
const failing = async (id: string) => {
attempts++
if (attempts < 3) throw new Error('fail')
return { id, name: 'John' }
}
const resilient = cruel.retry(failing, {
attempts: 3,
delay: 10
})
const user = await resilient('123')
expect(user.name).toBe('John')
expect(attempts).toBe(3)
})
})
Vitest Integration
Setup with Vitest:import { cruel } from 'cruel'
import { describe, test, beforeEach, afterEach, expect, vi } from 'vitest'
beforeEach(() => {
cruel.reset()
cruel.patchFetch()
})
afterEach(() => {
cruel.unpatchFetch()
})
describe('HTTP Client', () => {
test('handles rate limits with retry', async () => {
cruel.intercept('api.example.com', {
rateLimit: { rate: 0.5, retryAfter: 1 }
})
let attempts = 0
const fetchWithRetry = async () => {
attempts++
const response = await fetch('https://api.example.com/data')
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return response.json()
}
const resilient = cruel.retry(fetchWithRetry, {
attempts: 3,
delay: 100,
backoff: 'exponential'
})
try {
await resilient()
} catch {}
expect(attempts).toBeGreaterThan(1)
})
test('uses circuit breaker after threshold', async () => {
cruel.intercept('api.example.com', {
status: 500,
fail: 1
})
const apiFetch = async () => {
const response = await fetch('https://api.example.com/data')
if (!response.ok) throw new Error('API Error')
return response.json()
}
const breaker = cruel.circuitBreaker(apiFetch, {
threshold: 3,
timeout: 5000
})
for (let i = 0; i < 3; i++) {
try {
await breaker()
} catch {}
}
expect(breaker.getState().state).toBe('open')
})
})
Jest Integration
Setup with Jest:import { cruel } from 'cruel'
import { describe, test, beforeEach, expect } from '@jest/globals'
beforeEach(() => {
cruel.reset()
})
describe('Database Client', () => {
const db = {
query: async (sql: string) => {
return [{ id: 1, name: 'Test' }]
}
}
test('handles slow queries', async () => {
const slowQuery = cruel(db.query, { delay: 500 })
const start = Date.now()
await slowQuery('SELECT * FROM users')
const elapsed = Date.now() - start
expect(elapsed).toBeGreaterThanOrEqual(500)
})
test('times out long-running queries', async () => {
const verySlowQuery = cruel(db.query, { delay: 5000 })
const withTimeout = cruel.withTimeout(verySlowQuery, { ms: 1000 })
await expect(withTimeout('SELECT * FROM users'))
.rejects
.toThrow('cruel: injected timeout')
})
})
Custom Matchers
Use Cruel’s custom matchers:import { setupMatchers } from 'cruel/matchers'
import { cruel } from 'cruel'
import { test, beforeAll, expect } from 'bun:test'
beforeAll(() => {
setupMatchers()
})
test('custom matchers', async () => {
const fn = async () => 'test'
const failing = cruel.fail(fn, 1)
// Check if function throws Cruel errors
await expect(failing()).toEventuallyThrow()
// Check for specific error types
const timeout = cruel.timeout(fn, 1)
await expect(timeout()).toEventuallyThrow(CruelTimeoutError)
const networkError = cruel.network.disconnect(fn, 1)
await expect(networkError()).toEventuallyThrow(CruelNetworkError)
const httpError = cruel.http.status(fn, 500, 1)
await expect(httpError()).toEventuallyThrow(CruelHttpError)
})
AI SDK Testing
Test AI applications:import { describe, test, expect } from 'bun:test'
import { openai } from '@ai-sdk/openai'
import { generateText, streamText } from 'ai'
import { cruelModel, presets } from 'cruel/ai-sdk'
describe('AI Application', () => {
test('handles rate limits', async () => {
const model = cruelModel(openai('gpt-4o'), {
rateLimit: 1
})
await expect(generateText({
model,
prompt: 'Test'
})).rejects.toThrow(/429|rate/i)
})
test('retries on provider errors', async () => {
let attempts = 0
const attemptGenerate = async () => {
attempts++
const model = cruelModel(openai('gpt-4o'), {
overloaded: 0.5
})
return await generateText({
model,
prompt: 'Test'
})
}
for (let i = 0; i < 3; i++) {
try {
await attemptGenerate()
break
} catch (error) {
if (i === 2) throw error
}
}
expect(attempts).toBeLessThanOrEqual(3)
})
test('handles stream interruptions', async () => {
const model = cruelModel(openai('gpt-4o'), {
streamCut: 1
})
try {
const result = streamText({
model,
prompt: 'Count to 100'
})
for await (const _ of result.textStream) {
// Should throw
}
expect(true).toBe(false)
} catch (error) {
expect(error).toBeDefined()
}
})
})
Deterministic Testing
Use seeds for reproducible tests:import { cruel } from 'cruel'
import { describe, test, beforeEach, expect } from 'bun:test'
beforeEach(() => {
cruel.reset()
})
describe('Deterministic chaos', () => {
test('produces same results with same seed', () => {
// First run
cruel.seed(12345)
const results1 = Array(10).fill(0).map(() => cruel.coin(0.5))
// Second run with same seed
cruel.seed(12345)
const results2 = Array(10).fill(0).map(() => cruel.coin(0.5))
expect(results1).toEqual(results2)
})
test('chaos behaves predictably', async () => {
const fn = async () => 'ok'
cruel.seed(99999)
const wrapped = cruel.fail(fn, 0.5)
const results1 = []
for (let i = 0; i < 5; i++) {
try {
await wrapped()
results1.push('success')
} catch {
results1.push('failure')
}
}
cruel.seed(99999)
const results2 = []
for (let i = 0; i < 5; i++) {
try {
await wrapped()
results2.push('success')
} catch {
results2.push('failure')
}
}
expect(results1).toEqual(results2)
})
})
Scenario Testing
Test specific scenarios:import { cruel } from 'cruel'
import { describe, test, expect } from 'bun:test'
describe('Scenario testing', () => {
test('network partition', async () => {
cruel.scenario('networkPartition', {
chaos: { fail: 1 },
duration: 1000
})
cruel.play('networkPartition')
const fn = cruel(async () => 'ok')
// All calls should fail during partition
await expect(fn()).rejects.toThrow()
// Wait for scenario to end
await new Promise(r => setTimeout(r, 1100))
// Should work again
const result = await fn()
expect(result).toBe('ok')
})
test('degraded performance', async () => {
cruel.scenario('degraded', {
chaos: { delay: [500, 1500], fail: 0.1 }
})
cruel.play('degraded')
const fn = cruel(async () => 'ok')
const start = Date.now()
try {
await fn()
} catch {}
const elapsed = Date.now() - start
expect(elapsed).toBeGreaterThanOrEqual(500)
cruel.stop()
})
})
Snapshot Testing
Test chaos statistics:import { cruel } from 'cruel'
import { test, expect } from 'bun:test'
test('chaos statistics snapshot', async () => {
cruel.reset()
const fn = cruel(async () => 'ok', {
fail: 0.1,
delay: [50, 100]
})
for (let i = 0; i < 100; i++) {
try {
await fn()
} catch {}
}
const stats = cruel.stats()
expect(stats.calls).toBe(100)
expect(stats.failures).toBeGreaterThan(5)
expect(stats.failures).toBeLessThan(20)
expect(stats.avg).toBeGreaterThan(50)
expect(stats.avg).toBeLessThan(150)
})
Integration Test Example
Complete integration test:import { describe, test, beforeEach, afterEach, expect } from 'vitest'
import { cruel } from 'cruel'
class APIClient {
async fetch(endpoint: string) {
const response = await fetch(`https://api.example.com${endpoint}`)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return response.json()
}
async fetchWithRetry(endpoint: string, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await this.fetch(endpoint)
} catch (error) {
if (i === retries - 1) throw error
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)))
}
}
}
}
describe('APIClient Integration', () => {
let client: APIClient
beforeEach(() => {
cruel.reset()
cruel.patchFetch()
client = new APIClient()
})
afterEach(() => {
cruel.unpatchFetch()
})
test('handles realistic network conditions', async () => {
cruel.intercept('api.example.com', {
delay: [100, 500],
rateLimit: 0.1,
status: [500, 502],
fail: 0.05
})
const results = []
for (let i = 0; i < 10; i++) {
try {
const data = await client.fetchWithRetry('/data')
results.push('success')
} catch {
results.push('failure')
}
}
const successCount = results.filter(r => r === 'success').length
expect(successCount).toBeGreaterThan(0)
})
test('respects rate limits', async () => {
cruel.intercept('api.example.com', {
rateLimit: { rate: 0.5, retryAfter: 1 }
})
let rateLimited = 0
for (let i = 0; i < 10; i++) {
try {
await client.fetch('/data')
} catch (error) {
if (error.message.includes('429')) rateLimited++
}
}
expect(rateLimited).toBeGreaterThan(0)
})
})
Performance Testing
Measure performance under chaos:import { cruel } from 'cruel'
import { test, expect } from 'bun:test'
test('performance under chaos', async () => {
const fn = cruel(async () => {
// Simulate work
return 'ok'
}, {
delay: [10, 50],
fail: 0.1
})
const start = Date.now()
const results = await Promise.allSettled(
Array(100).fill(0).map(() => fn())
)
const elapsed = Date.now() - start
const successful = results.filter(r => r.status === 'fulfilled').length
const failed = results.filter(r => r.status === 'rejected').length
console.log({
total: 100,
successful,
failed,
elapsed: elapsed + 'ms',
avgPerRequest: (elapsed / 100).toFixed(2) + 'ms'
})
expect(successful).toBeGreaterThan(80)
expect(elapsed).toBeLessThan(10000)
})
Next Steps
- Learn about Production Readiness
- Explore Combining Patterns
- Master AI SDK Streaming