Deterministic flags in CI and unit tests
Use forTesting() + overrides for unit tests and a captured snapshot for integration and CI — flag-gated code that never touches the network.
A test that reads a flag over the network is flaky by construction: it depends on what the dashboard says right now, it fails in CI behind a firewall, and a teammate flipping a gate breaks an unrelated build. You want flag-gated code to be testable the way the rest of your code is — deterministic, offline, and explicit about what each case sets.
The SDK gives you two levels: forTesting() + overrides for unit tests, and a snapshot for integration and CI.
Unit tests — forTesting() + overrides
forTesting() builds a no-network client that's already "ready": init() / identify() / track() are no-ops, telemetry is off, and no SDK key is required. You seed each case explicitly:
import { Engine } from "@shipeasy/sdk/server";
const engine = Engine.forTesting();
engine.overrideFlag("new_checkout", true);
engine.overrideConfig("upload_limits", { max_uploads: 50 });
engine.overrideExperiment("hero_cta", "treatment", { primary_label: "Buy now" });
engine.getFlag("new_checkout", { user_id: "u1" }); // true
engine.getConfig("upload_limits"); // { max_uploads: 50 }
engine.track("u1", "purchase"); // no-op — never hits the network
engine.clearOverrides(); // reset between casesThe browser is identical via Engine.forTesting() from @shipeasy/sdk/client.
Integration / CI — a captured snapshot
When you want the real rule set — actual targeting, actual bucketing — without a live dependency, evaluate against a snapshot. A snapshot is just the two SDK wire bodies on disk:
import { Engine } from "@shipeasy/sdk/server";
// from a file (Node — reads with node:fs)
const engine = Engine.fromFile("./snapshot.json");
// or from an object you already hold (works anywhere)
const engine = Engine.fromSnapshot({ flags, experiments });
engine.getFlag("new_checkout", { user_id: "u1" }); // real eval, no networkUse forTesting() + override* to seed individual values; use a snapshot
when you want the real rules evaluated offline. override* applies on top of a
snapshot too, so you can take production rules and still force one specific case.
Rollout & measurement plan
Default to forTesting() for unit tests
Seed exactly the flags the unit under test reads. Each test states its own world.
Capture a snapshot for the integration suite
Save the bodies of GET /sdk/flags and GET /sdk/experiments; commit it
so CI gets real evaluation with no network.
Refresh the snapshot on purpose
A stale snapshot is a known, reviewable diff — re-capture when the rules you depend on change.
Invalidate a server cache the instant a config flips
Use onChange to drop a memoized render or a derived config on the next poll, so a dashboard change takes effect without a restart.
Migrate off another flag vendor via OpenFeature
Drop ShipeasyProvider in behind the OpenFeature API so your existing getBooleanValue(...) call sites keep working unchanged.