Shipeasy
SDKsReferenceTypeScript / JavaScript

Testing

For unit tests, swap the live configure() for configureForTesting() — a drop-in sibling with no network, ever (no SDK key required). It replaces the active…

Generated from the SDK's own /docs/ — also served raw at https://shipeasy-ai.github.io/sdk-ts/pages/testing.md.

For unit tests, swap the live configure() for configureForTesting() — a drop-in sibling with no network, ever (no SDK key required). It replaces the active configuration with a network-free engine, seeds the values your code should see, and is read through the ordinary new Client(user). In this mode the rules never fetch, track() is a no-op, assign() logs no exposure, and telemetry is off — your tests never touch the network.

import { configureForTesting, Client, clearOverrides } from "@shipeasy/sdk/server"; // or /client

// Seed everything the code under test should see (no key, no network):
configureForTesting({
  flags: { new_checkout: true },
  configs: { upload_limits: { max_uploads: 50 } },
});

const flags = new Client({ user_id: "u_1" }); // construct once per callsite
flags.getFlag("new_checkout");                 // true
flags.getConfig("upload_limits");              // { max_uploads: 50 }

clearOverrides(); // reset every seeded override back to the empty-blob default

To assert an experiment assignment, seed a real universe + experiment with configureForOffline() (below) — an experiment override refines an experiment that lives in a universe; it doesn't invent one in an empty universe. Read it with universe(name).assign():

const exp = new Client({ user_id: "u_1" }).universe("hero_cta").assign();
exp.enrolled;                 // true when the seeded experiment enrolled the unit
exp.group;                    // the assigned variant, or null
exp.get("primary_label", "Sign up"); // variant ?? universe default ?? fallback

configureForTesting() / configureForOffline() replace the active configuration (unlike configure(), which is first-config-wins), so a suite can reconfigure freely between cases.

Seed shapes

configureForTesting({ flags?, configs?, experiments?, attributes? }):

FieldShapeEffect
flags{ [name]: boolean }forced getFlag results
configs{ [name]: value }forced getConfig results
experiments{ [name]: [group, params] }forced enrolment for an experiment that exists in a universe (see below)
attributes(yourUser) => Usersame transform as configure() (default identity)

An experiments seed (and overrideExperiment) refines an experiment that already lives in a universe — it forces that experiment's variant. It does not invent an experiment in an empty universe, and it is read by universe, not by experiment name. Seed the universe + experiment via configureForOffline(), then force the variant. On an empty test-mode blob (no snapshot) universe().assign() returns not-enrolled regardless of the seed.

Package-level overrides (on the spot)

Layer a quick override on top of whatever configureForTesting() / configureForOffline() (or even a live configure()) set up. These are package-level — no object to hold:

import {
  overrideFlag,
  overrideConfig,
  overrideExperiment,
  clearOverrides,
} from "@shipeasy/sdk/server"; // or /client

overrideFlag("new_checkout", true);
overrideConfig("upload_limits", { max_uploads: 50 });
overrideExperiment("hero_cta", "treatment", { primary_label: "Buy now" });
// …read through `new Client(user)` …
clearOverrides(); // drop every on-the-spot override

A programmatic override always wins over the fetched/seeded value. In the browser the precedence is: programmatic override > URL/devtools override (?se_ks_… / ?se_cf_… / ?se_exp_…) > the server's evaluation.

Offline snapshot (server)

configureForOffline() evaluates the real rules from a captured snapshot with no network — init is a no-op and overrides still layer on top. Pass an in-memory snapshot object, or a path to a JSON file (Node-only — read with node:fs):

import { configureForOffline, Client } from "@shipeasy/sdk/server";

// From a JSON file on disk (Node only):
configureForOffline({ path: "./snapshot.json" });

// …or from an object you already hold (works anywhere):
configureForOffline({ snapshot: { flags, experiments } });

// …optionally force a specific variant of an experiment that exists in the
// snapshot's universe (overrides layer on top of the real rules):
configureForOffline({
  snapshot: { flags, experiments },
  experiments: { hero_cta: ["treatment", { primary_label: "Buy now" }] },
});

const flags = new Client({ user_id: "u1" }); // construct once per callsite
flags.getFlag("new_checkout");
flags.universe("default").assign().group; // the experiment's universe (see snapshot below)

Snapshot file shape

The snapshot is the two SDK wire bodies verbatim — { flags: <GET /sdk/flags body>, experiments: <GET /sdk/experiments body> }. A gate's rolloutPct is basis points (10000 = 100%); enabled is 1/0.

{
  "flags": {
    "version": "test-1",
    "plan": "free",
    "gates": {
      "new_checkout": {
        "rules": [],
        "rolloutPct": 10000,
        "salt": "s",
        "enabled": 1
      },
      "beta_banner": {
        "rules": [{ "attr": "plan", "op": "eq", "value": "pro" }],
        "rolloutPct": 5000,
        "salt": "s2",
        "enabled": 1
      }
    },
    "configs": {
      "upload_limits": { "value": { "max_uploads": 50 } }
    },
    "killswitches": {
      "payments": { "killed": 0 }
    }
  },
  "experiments": {
    "version": "test-1",
    "universes": {
      "default": { "holdout_range": null }
    },
    "experiments": {
      "hero_cta": {
        "universe": "default",
        "targetingGate": null,
        "allocationPct": 10000,
        "salt": "exp-salt",
        "status": "running",
        "groups": [
          { "name": "control", "weight": 5000, "params": { "primary_label": "Sign up" } },
          { "name": "treatment", "weight": 5000, "params": { "primary_label": "Buy now" } }
        ]
      }
    }
  }
}

i18n keys in tests

Assertions on translated copy are brittle — they break when the copy is edited. Under env==test (SHIPEASY_ENV / NODE_ENV, i.e. jest/vitest) the SDK defaults to renderKeysOnly, so i18n.t("checkout.cta", "Place order") returns the key "checkout.cta" and your tests assert against stable data with no setup. Force it on/off with configure({ i18n: { renderKeysOnly } }) or i18n.configure({ renderKeysOnly }) — see i18n → Render keys only.

Was this page helpful?
Updated July 25, 2026

On this page