@avsbhq/test
v1.0.0
Published
Test utilities for the [A vs B](https://app.avsb.cloud) SDK.
Downloads
196
Readme
@avsbhq/test
Test utilities for the A vs B SDK.
Provides a mock client, a React test provider, a fluent flag builder, evaluation recorders, and datafile fixtures so you can write deterministic unit and component tests without a live SDK connection.
1. Install
npm install --save-dev @avsbhq/testWorks with vitest and jest. Installs @avsbhq/core as a peer dependency — you do not need to install it separately.
Optional vitest / jest plugins:
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import avsbPlugin from '@avsbhq/test/vitest'
export default defineConfig({
plugins: [avsbPlugin()],
test: { globals: true },
})// jest.config.js
module.exports = {
preset: '@avsbhq/test/jest',
}2. Quickstart
import { createMockClient, TestData } from '@avsbhq/test'
// 1. Describe your flag configuration
const td = TestData.flag('checkout-v2')
.booleanFlag()
.variationForUser('u_paying', true)
.fallthroughVariation(false)
// 2. Create the mock client
const client = createMockClient({ flags: [td.build()] })
// 3. Evaluate
const flag = client.getBoolFlag('checkout-v2', false)
expect(flag.value).toBe(false) // 'u_paying' not yet identified
client.identify({ kind: 'user', key: 'u_paying' })
const paidFlag = client.getBoolFlag('checkout-v2', false)
expect(paidFlag.value).toBe(true)3. createMockClient
Creates an in-memory mock that implements the same interface as AvsbClient (browser) and AvsbServer (server). Drop it into any code that accepts an SDK client.
import { createMockClient } from '@avsbhq/test'
import type { MockClientOptions } from '@avsbhq/test'
const client = createMockClient({
// Flag map: flagKey → value (serves as the fallthrough / default variation)
flags: { 'dark-mode': true, 'theme': 'midnight', 'max-results': 50 },
// Per-user overrides: key is 'flagKey:userKey'
flagsByUser: { 'checkout-v2:u_trial': false, 'checkout-v2:u_paid': true },
// Initial context (default: { kind: 'user', key: 'test-user' })
context: { kind: 'user', key: 'u_1', plan: 'pro' },
// Whether the client is immediately ready (default: true)
ready: true,
})The mock client exposes all public methods: getFlag, getBoolFlag, getStringFlag, getNumberFlag, getJsonFlag, getAllFlags, identify, updateAttributes, getContext, reset, track, on, onReady, close, flush.
Test-only methods on MockClient
// Inspect tracked events
client._tracked()
// [{ eventKey: 'checkout_clicked', payload: { value: 99 } }]
// Inspect evaluations
client._evaluations()
// [{ flagKey: 'checkout-v2', value: true, contextKey: 'u_paid' }]
// Mutate a flag at runtime (triggers flagChange event)
client._setFlag('dark-mode', false)
// Simulate deferred readiness
const unready = createMockClient({ ready: false })
unready._markReady({ success: true, source: 'network' })4. TestData — fluent flag builder
TestData builds FlagDatafileEntry-shaped objects for use with createMockClient or fixture datafiles.
import { TestData } from '@avsbhq/test'
const boolFlag = TestData.flag('checkout-v2')
.booleanFlag()
.variationForUser('u_1', true) // user key override
.variationForContext('organization', 'org_1', true) // context-kind override
.fallthroughVariation(false) // default for all other users
.build()
const strFlag = TestData.flag('theme')
.stringFlag()
.variationForUser('u_dark', 'midnight')
.fallthroughVariation('light')
.build()
const jsonFlag = TestData.flag('api-config')
.jsonFlag()
.fallthroughVariation({ timeout: 5000, retries: 3 })
.build()Pass the built entries to createMockClient:
const client = createMockClient({
flags: [boolFlag, strFlag, jsonFlag],
})5. AvsbTestProvider — React component tests
Wraps the React component tree with a mock client. Works with @avsbhq/react, @avsbhq/next, and @avsbhq/react-native.
import { render, screen } from '@testing-library/react'
import { AvsbTestProvider } from '@avsbhq/test'
import { CheckoutButton } from './CheckoutButton'
test('shows new checkout when flag on', () => {
render(
<AvsbTestProvider mockFlags={{ 'new-checkout-flow': true }}>
<CheckoutButton />
</AvsbTestProvider>
)
expect(screen.getByRole('button')).toHaveTextContent('New checkout')
})
test('shows default checkout when flag off', () => {
render(
<AvsbTestProvider mockFlags={{ 'new-checkout-flow': false }}>
<CheckoutButton />
</AvsbTestProvider>
)
expect(screen.getByRole('button')).toHaveTextContent('Checkout')
})
test('shows correct variant for paying user', () => {
const td = TestData.flag('new-checkout-flow')
.booleanFlag()
.variationForUser('u_paid', true)
.fallthroughVariation(false)
render(
<AvsbTestProvider
client={createMockClient({
flags: [td.build()],
context: { kind: 'user', key: 'u_paid' },
})}
>
<CheckoutButton />
</AvsbTestProvider>
)
expect(screen.getByRole('button')).toHaveTextContent('New checkout')
})AvsbTestProvider accepts either mockFlags (a simple key→value map) or a pre-built client (a MockClient from createMockClient) for full control.
6. recordEvaluations — assertion helpers
Attaches a recorder to a mock client and exposes Vitest/jest-compatible assertion helpers:
import { recordEvaluations, createMockClient } from '@avsbhq/test'
const client = createMockClient({ flags: { 'checkout-v2': true } })
const recorder = recordEvaluations(client)
// Run code under test
myCodeUnderTest(client)
// Assert evaluations
expect(recorder).toHaveEvaluated('checkout-v2')
expect(recorder).toHaveEvaluated('checkout-v2')
.withContext({ kind: 'user', key: 'u_1' })
.withValue(true)
// Assert tracking
expect(recorder).toHaveTracked('purchase')
expect(recorder).toHaveTracked('purchase').withValue(199.0)
expect(recorder).toHaveTracked('purchase').withProperties({ plan: 'annual' })
// Assert NOT called
expect(recorder).not.toHaveEvaluated('unknown-flag')
// Inspect raw records
recorder.evaluations()
// [{ flagKey, value, contextKey }]
recorder.tracked()
// [{ eventKey, payload }]7. Fixture factories
For unit tests that need raw datafile or context objects:
import { createTestDatafile, createTestContext, createMultiTestContext } from '@avsbhq/test'
// Minimal FlagDatafile with the given flags as boolean flags
const datafile = createTestDatafile({
'checkout-v2': true,
'dark-mode': false,
})
// Minimal single-kind EvalContext
const ctx = createTestContext('u_123', { plan: 'pro' })
// { kind: 'user', key: 'u_123', plan: 'pro' }
// Multi-kind EvalContext
const multiCtx = createMultiTestContext({
user: { key: 'u_123', plan: 'pro' },
organization: { key: 'org_456', tier: 'enterprise' },
})8. Flag<T> fixture builders (from @avsbhq/core)
For tests that handle Flag<T> objects directly (not via a mock client), import the factory from @avsbhq/core:
import { createFlag, notFoundFlag } from '@avsbhq/core'
const FLAG_ON = createFlag<boolean>({
value: true,
variationKey: 'on',
source: 'rule',
ruleId: 'r1',
ruleType: 'ab_test',
reasons: ['matched audience: paid-users'],
})
const FLAG_MISSING = notFoundFlag<boolean>(false, 'flag not in datafile')9. SSR / hydration
Not applicable to @avsbhq/test. For Next.js RSC testing, use createTestDatafile with evaluateFlagServer from @avsbhq/next/server:
import { evaluateFlagServer } from '@avsbhq/next/server'
import { createTestDatafile } from '@avsbhq/test'
const df = createTestDatafile({ 'checkout-v2': true })
const flag = evaluateFlagServer(df, { kind: 'user', key: 'u_1' }, 'checkout-v2', false)
expect(flag.value).toBe(true)10. Graceful shutdown
Not applicable — mock clients resolve close() and flush() immediately as no-ops.
11. Testing the test utilities
The mock client and TestData builder are themselves tested in packages/avsb-test/src/**/*.test.ts. When extending them, follow the same colocated test pattern.
12. Migration
From LaunchDarkly LDTestData
| LaunchDarkly LDTestData | @avsbhq/test |
|---|---|
| LDTestData.dataSource() | createMockClient({ flags: [...] }) |
| td.flag('key').booleanFlag() | TestData.flag('key').booleanFlag() |
| td.flag('key').variationForUser(user, true) | TestData.flag('key').variationForUser(userId, true) |
| td.flag('key').fallthroughVariation(false) | TestData.flag('key').fallthroughVariation(false) |
| td.flag('key').build() passed to init | createMockClient({ flags: [td.build()] }) |
| client.flush() in test cleanup | await client.flush() (no-op for mock) |
From Statsig local bootstrap
| Statsig local bootstrap | @avsbhq/test |
|---|---|
| new StatsigServer(key, { localMode: true }) | createMockClient({ flags: {...} }) |
| override.overrideGate('key', true) | createMockClient({ flags: { 'key': true } }) |
| override.overrideExperiment('key', {...}) | TestData.flag('key').fallthroughVariation({...}).build() |
| statsig.logEvent(user, 'event') → check with getEventLogQueue | client.track(...) → check with client._tracked() |
