Multi-Adapter Testing

Run the same test against multiple implementations — in-memory, HTTP API, and browser UI. Define behavior once, verify it everywhere.

Setup

You need a domain and at least two adapters. This guide uses a task board example with three adapters.

Domain

// domains/task-board.ts
import { defineDomain, action, query, assertion } from '@averspec/core'

export const taskBoard = defineDomain({
  name: 'task-board',
  actions: {
    createTask: action<{ title: string }>(),
    moveTask: action<{ title: string; status: string }>(),
  },
  queries: {},
  assertions: {
    taskInStatus: assertion<{ title: string; status: string }>(),
  },
})

Unit Adapter

Tests against in-memory objects. Runs in ~1ms.

// adapters/task-board.unit.ts
import { adapt, unit } from '@averspec/core'
import { expect } from 'vitest'
import { Board } from '../src/board'
import { taskBoard } from '../domains/task-board'

export const unitAdapter = adapt(taskBoard, {
  protocol: unit(() => new Board()),
  actions: {
    createTask: async (board, { title }) => board.create(title),
    moveTask: async (board, { title, status }) => board.move(title, status),
  },
  queries: {},
  assertions: {
    taskInStatus: async (board, { title, status }) => {
      const task = board.details(title)
      expect(task?.status).toBe(status)
    },
  },
})

HTTP Adapter

Tests against a REST API. Runs in ~10ms.

// adapters/task-board.http.ts
import { adapt } from '@averspec/core'
import { expect } from 'vitest'
import { http } from '@averspec/protocol-http'
import { taskBoard } from '../domains/task-board'

export const httpAdapter = adapt(taskBoard, {
  protocol: http({ baseUrl: 'http://localhost:3000' }),
  actions: {
    createTask: async (ctx, { title }) => {
      await ctx.post('/tasks', { title })
    },
    moveTask: async (ctx, { title, status }) => {
      await ctx.patch(`/tasks/${encodeURIComponent(title)}`, { status })
    },
  },
  queries: {},
  assertions: {
    taskInStatus: async (ctx, { title, status }) => {
      const res = await ctx.get(`/tasks/${encodeURIComponent(title)}`)
      const task = await res.json()
      expect(task.status).toBe(status)
    },
  },
})

Playwright Adapter

Tests against a browser UI. Runs in ~300ms.

// adapters/task-board.playwright.ts
import { adapt } from '@averspec/core'
import { expect } from '@playwright/test'
import { playwright } from '@averspec/protocol-playwright'
import { taskBoard } from '../domains/task-board'

export const playwrightAdapter = adapt(taskBoard, {
  protocol: playwright(),
  actions: {
    createTask: async (page, { title }) => {
      await page.getByPlaceholder('Task title').fill(title)
      await page.getByRole('button', { name: 'Add' }).click()
    },
    moveTask: async (page, { title, status }) => {
      await page.getByTestId(`task-${title}`).dragTo(
        page.getByTestId(`column-${status}`)
      )
    },
  },
  queries: {},
  assertions: {
    taskInStatus: async (page, { title, status }) => {
      const column = page.getByTestId(`column-${status}`)
      await expect(column.getByText(title)).toBeVisible()
    },
  },
})

Register All Adapters

// aver.config.ts
import { defineConfig } from '@averspec/core'
import { unitAdapter } from './adapters/task-board.unit'
import { httpAdapter } from './adapters/task-board.http'
import { playwrightAdapter } from './adapters/task-board.playwright'

export default defineConfig({
  adapters: [unitAdapter, httpAdapter, playwrightAdapter],
})

Write Tests Once

The test file imports the domain, never the adapters:

// vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    setupFiles: ['./aver.config.ts'],
  },
})
// tests/task-board.spec.ts
import { suite } from '@averspec/core'
import { taskBoard } from '../domains/task-board'

const { test } = suite(taskBoard)

test('create and move a task', async ({ act, assert }) => {
  await act.createTask({ title: 'Fix login bug' })
  await assert.taskInStatus({ title: 'Fix login bug', status: 'backlog' })
  await act.moveTask({ title: 'Fix login bug', status: 'in-progress' })
  await assert.taskInStatus({ title: 'Fix login bug', status: 'in-progress' })
})

Run

npx aver run
 ✓ tests/task-board.spec.ts
   ✓ create and move a task [unit]           1ms
   ✓ create and move a task [http]          14ms
   ✓ create and move a task [playwright]   312ms

One test, three adapters, three levels of confidence.

Filtering

Run a specific adapter:

npx aver run --adapter unit
npx aver run --adapter http

Run a specific domain:

npx aver run --domain task-board

MIT License © 2026 Nate Jackson

This site uses Just the Docs, a documentation theme for Jekyll.