Testland
Browse all skills & agents

amplitude-experiment-test

Wraps Amplitude Experiment SDK testing patterns: client initialization with API key (or a bootstrapped local flag config for offline tests), the fetch / variant API, exposure-event suppression in tests, and assignment-integrity tests. Use when writing tests for code that uses Amplitude Experiment for A/B testing or flag management.

Install with skills.sh (any agent)

npx skills add testland/qa --skill amplitude-experiment-test
View source

amplitude-experiment-test

Overview

Per amplitude.com/docs/experiment (opens in new window), the Amplitude Experiment SDKs (server-side and client-side) expose fetch + variant APIs: fetch the user's assigned variants, then read each variant on demand.

Amplitude correlates exposure + outcome events via the same user ID space as Amplitude Analytics, so exposure-event suppression in tests is important to avoid polluting analytics.

When to use

  • Tests for code that reads an Amplitude Experiment variant.
  • Suppressing exposure events in non-production test runs.
  • Assignment-integrity tests per ab-test-validity-checklist Step 3.

Authoring

Install

pip install amplitude-experiment           # Python (server-side)
npm install --save-dev @amplitude/experiment-node-server

Initialize (server-side)

import * as Experiment from '@amplitude/experiment-node-server';

const client = Experiment.Experiment.initializeRemote(API_KEY, {
  // Suppress real fetches in tests
  fetchTimeoutMillis: 1000,
});

For fully-offline tests, use the local evaluation mode and seed the flag config via the bootstrap option. Per the local-evaluation docs (opens in new window), start() takes no arguments and always performs an initial network fetch (it throws offline and would clear a bootstrapped cache), so do NOT call it for a no-network test: bootstrap populates the cache in the constructor.

import { LocalEvaluationClient } from '@amplitude/experiment-node-server';
import { readFileSync } from 'fs';

// Commit the flag config the flags endpoint would return, keyed by flag key.
const flagFixture = JSON.parse(readFileSync('fixtures/flags.json', 'utf8'));

const localClient = new LocalEvaluationClient(API_KEY, {
  bootstrap: flagFixture,   // seeds the cache; no start() / no network
});

Read variant (offline, synchronous)

evaluateV2 reads straight from the bootstrapped cache, no fetch required:

const user = { user_id: 'user-1', device_id: 'dev-1' };

test('user variant from local eval', () => {
  const variants = localClient.evaluateV2(user);
  expect(variants['checkout-experiment'].value).toBe('treatment-a');
});

Force a variant for a test

Amplitude Experiment's standard pattern is via the flag config: override the flag's default-variant for a specific user ID by modifying the local-eval fixture. Alternatively, mock the evaluate method:

import { jest } from '@jest/globals';

test('user in treatment', () => {
  jest.spyOn(localClient, 'evaluateV2').mockReturnValue({
    'checkout-experiment': { value: 'treatment-a' } as any,
  });

  const variants = localClient.evaluateV2(user);
  expect(variants['checkout-experiment'].value).toBe('treatment-a');
});

Suppress exposure events in tests

Default behavior fires an exposure event on variant() read. Suppress per amplitude.com/docs/experiment (opens in new window):

// In test setup:
const client = Experiment.Experiment.initializeRemote(API_KEY, {
  // Disable automatic exposure tracking
  automaticExposureTracking: false,
});

Assignment integrity tests

test('deterministic assignment', () => {
  const v1 = localClient.evaluateV2({ user_id: 'user-1' });
  const v2 = localClient.evaluateV2({ user_id: 'user-1' });
  expect(v1).toEqual(v2);
});

Running

npm test

CI integration

jobs:
  amplitude-experiment-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npm test
        env:
          AMPLITUDE_API_KEY: ${{ secrets.AMPLITUDE_TEST_KEY }}

For fully-offline CI: skip the env var and use local-eval with checked-in flag config JSON.

Anti-patterns

Anti-patternWhy it failsFix
Tests use prod Amplitude keyTest users pollute analyticsUse test workspace + dev key
Exposure events enabled in CISpurious exposure trackingautomaticExposureTracking: false
Mocking variant() result without testing the fetchMisses fetch-network bugsTest both layers separately
Local-eval flag JSON not committedTest flakes when prod changesCommit fixture
Skipping client.stop() / cleanupNetwork handles leakAlways teardown
Different user-ID space between test + analyticsAmplitude correlation brokenMatch the prod user-ID strategy

Limitations

  • Local-evaluation mode is feature-limited. Some flag types (CMAB, multi-armed bandit) aren't supported offline.
  • Mocking variant() loses targeting-rule fidelity. Use real local-eval when targeting matters.
  • Exposure suppression is binary. Can't selectively suppress per-test.
  • Doesn't validate Amplitude's results analysis. Platform-side statistics separate.

References

Related skills

ab-test-validity-checklist

Workflow skill that builds an A/B-test validity checklist from an experiment proposal, walking the canonical design-correctness gates - pre-registered OEC/power/guardrails, randomization unit + SRM check, assignment integrity, telemetry, peeking discipline, novelty/primacy, post-experiment SRM re-check - into a per-experiment checklist + sign-off form. Use when launching, auditing, or governing an experiment. For pitfall mechanics use guardrail-metrics-reference or peeking-problem-reference; to read an already-valid result use experiment-results-interpreter; for per-SDK harness tests use optimizely-test or statsig-test - this gates DESIGN, not SDK code.

experiment-results-interpreter

Interprets the results of a valid online controlled experiment, one whose harness, SRM, and telemetry have already been confirmed. Covers the distinction between practical and statistical significance, reading confidence intervals instead of binary p-values, novelty and primacy week-over-week decay that causes post-ship reversion, interaction effects from concurrent experiments, Simpson's paradox in segmented results, and the ordered guardrail-check sequence required before a ship decision. Use when a data scientist or PM is ready to draw conclusions from an experiment whose telemetry and randomisation have already passed the ab-test-validity-checklist. Distinct from ab-test-validity-checklist (harness setup and SRM detection) and from interaction-effect overlap auditing during experiment design.

guardrail-metrics-reference

Pure-reference catalog of guardrail-metric methodology for online controlled experiments. Defines guardrail metrics (metrics that must NOT degrade for an experiment to ship, even if the primary metric improves), the standard guardrail set (latency / errors / engagement / opt-out), the relationship to OEC (Overall Evaluation Criterion) per Kohavi et al., and pre-commitment of the metric set. The quantitative evaluation mechanics (per-metric alert/block thresholds, Bonferroni / Benjamini-Hochberg multiple-comparison correction) live in references/. Use when designing the metric set for a new experiment, auditing existing experiment configs, or reviewing experiment results before ship-decisions.

optimizely-test

Wraps Optimizely Feature Experimentation SDK testing patterns - client init from a fixture datafile (offline-friendly), the decide / decideAll v5 API, forced-decisions for per-test arm pinning (fixing which variation a user gets), OptimizelyUserContext + activate/track events, assignment-integrity (deterministic bucketing) tests. Use when writing A/B tests or feature-flag tests for Optimizely-instrumented application code. For another experimentation SDK use the matching harness - statsig-test, vwo-test, amplitude-experiment-test, or split-io-test; for experiment DESIGN gates not SDK code use ab-test-validity-checklist.

peeking-problem-reference

Pure-reference catalog of the peeking problem in online A/B testing. Defines the problem (repeatedly looking at experiment results inflates the false-positive rate above the declared alpha because each look is a separate test), the canonical mitigations (fixed-horizon test with pre-declared sample size; sequential testing with alpha-spending functions e.g., O'Brien-Fleming, Pocock; always-valid inference / mSPRT per Johari et al.), and the policy choices (data-peek schedule, stop-early thresholds, decision-time guard rails). Use when designing an experimentation platform's stop-early policy or auditing why a result was declared significant.

split-io-test

Wraps Split.io (Harness FME) SDK testing patterns: hermetic localhost/offline mode with an in-memory features map (JavaScript/browser) or a YAML fixture file (Node.js server-side), getTreatment and getTreatmentWithConfig evaluation, the SDK_READY event and whenReady() promise, impression listener verification, sync.impressionsMode configuration, and CI setup. Use when writing tests for application code instrumented with the Split.io or Harness Feature Management & Experimentation SDK.

statsig-test

Wraps Statsig SDK testing patterns - server-side statsig.initialize with an API key, gate / experiment / dynamic-config evaluation (checkGate, getExperiment, getConfig), local-evaluation offline mode, overrideGate / overrideConfig to force a user into an arm, assignment-integrity tests. Use when writing tests for Statsig-instrumented application code. For another experimentation SDK use the matching harness - optimizely-test, vwo-test, amplitude-experiment-test, or split-io-test; for experiment DESIGN gates not SDK code use ab-test-validity-checklist.

vwo-test

Wraps VWO (Visual Website Optimizer) SDK testing patterns: SDK initialization with the settings file (offline-capable), `getFeatureVariableValue` and `activate` API, force-bucketing for per-test assignment, and assignment-integrity tests against the bucketing algorithm. Use when writing tests for VWO-instrumented application code.