Use when code instrumented with Statsig, Optimizely, Split.io (Harness FME), Amplitude Experiment or VWO needs tests. Initializes the SDK offline from a committed config fixture, pins each test to an arm and checks assignment integrity, with per-vendor references.
97%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Every major experimentation SDK ships the same hermetic-test mechanism under a different name: a point-in-time config fixture (datafile, settings file, flag payload, features map) that the SDK evaluates locally, so tests make zero network calls, pollute no production analytics, and stay deterministic. The vendor-specific mechanics differ only in how the fixture is loaded and how an arm is pinned.
| SDK | Offline mechanism | Arm pinning | Reference |
|---|---|---|---|
| Statsig | localMode: true | overrideGate / overrideConfig | references/statsig.md |
| Optimizely | JSON datafile fixture | set_forced_decision | references/optimizely.md (+ optimizely-recipes.md) |
| Split.io / Harness FME | authorizationKey: 'localhost' + features map / YAML | Per-key fixture entry (no override API) | references/split-io.md (+ split-io-example.md) |
| Amplitude Experiment | LocalEvaluationClient + bootstrap | Fixture edit or evaluateV2 mock | references/amplitude.md |
| VWO | Settings file + is_development_mode | Deterministic bucketing on user ID | references/vwo.md |
ab-test-validity-checklist Step 3.Regardless of vendor, the suite has the same five steps:
tests/fixtures/ and
refreshed deliberately (drift between fixture and prod config is
invisible otherwise).localMode, datafile string, 'localhost' key, bootstrap,
is_development_mode).Plus two integrity tests every suite should carry:
The team ships a new_checkout_flow flag with a treatment_a variation
and needs a deterministic test that a premium-plan user is routed into the
treatment:
import json
from optimizely import optimizely
# Step 1-2: committed fixture, offline init - no SDK key, no network
with open("tests/fixtures/optimizely-datafile.json") as f:
client = optimizely.Optimizely(f.read())
def test_premium_user_in_treatment():
# Step 3: context carries the attributes targeting needs
user = client.create_user_context("user-1", {"plan": "premium"})
decision = user.decide("new_checkout_flow")
# Step 4: assert on enabled + variation_key, not IDs
assert decision.enabled is True
assert decision.variation_key == "treatment_a"
def test_assignment_deterministic():
user = client.create_user_context("user-1")
d1 = user.decide("new_checkout_flow")
d2 = user.decide("new_checkout_flow")
assert d1.variation_key == d2.variation_keyThe fixture drives the whole decision; the same shape translates to each vendor via its reference above.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Live API key in tests | Production analytics polluted; rate limits; flakes | The vendor's offline switch |
| Fixture not version-controlled | Tests flake when prod config changes | Commit; refresh deliberately |
| Overrides / forced decisions leak across tests | Cross-test pollution | Per-test context + cleanup |
| Asserting on internal config / variation IDs | IDs change per environment | Assert keys and values |
| Skipping client shutdown / destroy | Event-flush timers and handles leak | Teardown in afterAll |
| Trusting one user ID to cover both arms | May bucket into one arm only | Distribution test across many IDs |
ab-test-validity-checklist.experiment-results-interpreter.sample-ratio-mismatch-detector agent.