[iris]
Guides

Testing

Instant test environment reset with fork

The Problem

Traditional test environments suffer from state pollution:

  • Tests affect each other through shared database state
  • File system changes persist between runs
  • Flaky tests due to execution ordering

The Iris Solution

Keep one seeded base sandbox alive. Each test forks from it — fully isolated, zero setup overhead per test.

import { Sandbox } from '@iris/sdk'
import { describe, it, beforeAll, afterAll, afterEach } from 'vitest'

let base: Sandbox

beforeAll(async () => {
  base = await Sandbox.create()

  // Install deps, seed database, etc.
  await base.exec.run('npm install')
  await base.exec.run('npm run db:seed')
})

afterAll(async () => {
  await base.kill()
})

describe('User API', () => {
  let env: Sandbox

  beforeEach(async () => {
    // Each test starts from the same clean state
    env = await base.fork()
  })

  afterEach(async () => {
    await env.kill()
  })

  it('creates a user', async () => {
    const result = await env.exec.run('npm test -- user.create.test.ts')
    expect(result.exit_code).toBe(0)
  })

  it('deletes a user', async () => {
    const result = await env.exec.run('npm test -- user.delete.test.ts')
    expect(result.exit_code).toBe(0)
  })
})

Parallel Test Execution

Fork from the same base in parallel — no interference:

const tests = [
  'auth.test.ts',
  'users.test.ts',
  'payments.test.ts',
  'notifications.test.ts',
]

const results = await Promise.all(
  tests.map(async (test) => {
    const env = await base.fork()
    const result = await env.exec.run(`npm test -- ${test}`)
    await env.kill()
    return { test, passed: result.exit_code === 0, output: result.stdout }
  }),
)

const failed = results.filter((r) => !r.passed)
if (failed.length > 0) {
  console.error('Failed:', failed.map((r) => r.test))
}

Integration Testing

Test against real services with a guaranteed-clean environment per suite:

async function integrationTest() {
  const base = await Sandbox.create()

  // Start services once
  await base.exec.run('docker-compose up -d')
  await base.exec.run('python3 wait_for_services.py')

  // Each suite gets a fresh fork — database is in the same state for all
  await Promise.all(
    testSuites.map(async (suite) => {
      const env = await base.fork()
      try {
        const result = await env.exec.run(`pytest ${suite}`)
        if (!result.ok) console.error(suite, result.stderr)
      } finally {
        await env.kill()
      }
    }),
  )

  await base.kill()
}

CI/CD Integration

GitHub Actions

name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install SDK
        run: npm install @iris/sdk

      - name: Run Tests
        env:
          IRIS_API_KEY: ${{ secrets.IRIS_API_KEY }}
        run: npm test

Performance Comparison

ApproachSetup TimePer-Test Overhead
Docker30-60s5-10s
VM Snapshot10-30s2-5s
Iris ForkOnce<1ms

Iris forks are 1000x faster than traditional VM snapshots because only the pages a test actually writes are copied — everything else is shared read-only with the base.

On this page