# Test flags locally

Exercise flag branches without production credentials or network requests.

The Node.js SDK accepts an in-memory snapshot. Tests and local tools can evaluate immediately without
a runtime key, network request, or `initialize()`.

Use local snapshots for unit tests and deterministic developer workflows. Use a dedicated hosted
environment only when the test must verify the real API or snapshot contract.

## Create an in-memory client

```ts
import { FeatureGate } from "@featuregate/node";

const featuregate = new FeatureGate({
  flags: {
    "new-checkout": { defaultValue: true },
  },
});

expect(featuregate.getBooleanValue("new-checkout", false)).toBe(true);
```

Pure in-memory clients are ready immediately. They do not poll and do not need remote initialization.

## Test every meaningful branch

For each guarded decision, test:

- The configured new value.
- The configured existing value.
- The caller default when the flag is absent.
- A wrong getter type when that failure is important.
- Matching and non-matching targeting context.
- Missing attributes used by a rule.

Test business behaviour rather than the SDK implementation. An application test should verify what
the checkout does when enabled, not only that a getter returned `true`.

## Use deterministic contexts

Percentage allocation is deterministic only for stable inputs. Use fixed opaque identifiers in tests
and assert the observed branch.

Do not generate random rollout IDs inside a test. A random value can move between buckets and create
flaky results that reveal nothing about application correctness.

Keep context fixtures small and explicit:

```ts
const staffContext = {
  targetingKey: "user_test_staff",
  attributes: {
    user: { id: "user_test_staff", role: "staff" },
    account: { id: "account_test", plan: "pro" },
  },
};
```

## Isolate tests

Create a client inside each test or fixture. Do not mutate one shared snapshot across tests because
order-dependent state can hide cleanup and targeting mistakes.

Close any client configured with polling. Pure in-memory clients do not own network resources, but a
consistent cleanup pattern keeps fixtures safe if their configuration changes later.

Do not reuse production flag keys merely because they are familiar. Test fixtures should describe the
application contract and remain independent from production configuration.

## Integration tests

Use a dedicated non-production environment when validating authentication, snapshot parsing, ETag
refresh, hosted targeting, or the HTTP response contract.

Create a minimum-scope credential and inject it through CI secrets. Never print it on failure. Do not
use production credentials or permit CI to mutate production configuration.

Tests that create or change hosted resources must restore or delete their resources. Record enough
metadata to clean up after interruption without logging raw credentials.

## Test cleanup itself

Temporary flags should disappear from tests when guarded code is removed. A fixture that still needs
the retired key is evidence that cleanup is incomplete.

Add assertions for the final unguarded behaviour before deleting obsolete flag branches. Then remove
the test variants that only existed to exercise the retired branch.

## Common mistakes

- Calling `initialize()` on an in-memory-only client when no remote load is needed.
- Sharing mutable clients across parallel tests.
- Using random rollout identifiers.
- Treating caller-default tests as proof that hosted configuration is correct.
- Putting a production key in a local `.env` or CI log.

Related pages: [Node.js SDK](/docs/sdk/node),
[Design evaluation context](/docs/flags/evaluation-context), and
[Production integration checklist](/docs/evaluate/production-checklist).
