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.delete()
})
describe('User API', () => {
let env: Sandbox
beforeEach(async () => {
env = await base.fork()
})
afterEach(async () => {
await env.delete()
})
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)
})
})import pytest
from iris import IrisClient
client = IrisClient()
@pytest.fixture(scope="session")
def base():
sandbox = client.sandboxes.create()
sandbox.exec.run(["bash", "-c", "npm install && npm run db:seed"])
yield sandbox
sandbox.delete()
@pytest.fixture
def env(base):
fork = base.fork()
yield fork
fork.delete()
def test_creates_user(env):
result = env.exec.run(["npm", "test", "--", "user.create.test.ts"])
assert result.exit_code == 0
def test_deletes_user(env):
result = env.exec.run(["npm", "test", "--", "user.delete.test.ts"])
assert result.exit_code == 0Parallel 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.delete()
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))
}import asyncio
from iris import AsyncIrisClient
async def main():
client = AsyncIrisClient()
# assume base sandbox is already set up
tests = ["auth.test.ts", "users.test.ts", "payments.test.ts", "notifications.test.ts"]
async def run_test(test):
env = await base.fork()
result = await env.exec.run(["bash", "-c", f"npm test -- {test}"])
await env.delete()
return {"test": test, "passed": result.exit_code == 0, "output": result.stdout}
results = await asyncio.gather(*[run_test(t) for t in tests])
failed = [r for r in results if not r["passed"]]
if failed:
print("Failed:", [r["test"] for r in failed])
asyncio.run(main())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.delete()
}
}),
)
await base.delete()
}import asyncio
from iris import AsyncIrisClient
async def integration_test():
client = AsyncIrisClient()
base = await client.sandboxes.create()
# Start services once
await base.exec.run(["bash", "-c", "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
async def run_suite(suite):
env = await base.fork()
try:
result = await env.exec.run(["pytest", suite])
if not result.ok:
print(suite, result.stderr)
finally:
await env.delete()
await asyncio.gather(*[run_suite(s) for s in test_suites])
await base.delete()
asyncio.run(integration_test())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 testPerformance Comparison
| Approach | Setup Time | Per-Test Overhead |
|---|---|---|
| Docker | 30-60s | 5-10s |
| VM Snapshot | 10-30s | 2-5s |
| Iris Fork | Once | <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.