DocsGuides

Guides

Environments and instrumentation testing

4 min readReviewed July 2026

Test events should never make a production conversion rate mysterious. Choose an environment boundary before multiple applications and pipelines begin sending data, then make that boundary part of your shared tracking code.

Choose an isolation model#

GraphJSON supports two practical approaches.

Separate workspaces#

Use one workspace for production and another for non-production data. This is the strongest boundary because each workspace has its own API key, collections, dashboards, queries, alerts, and team access.

This model works best when:

  • production access should be limited
  • staging should closely mirror production
  • you want the same collection names in every environment
  • accidental cross-environment queries are unacceptable

Store the correct API key in each deployment environment. Never expose either key in browser code.

Separate collections#

Use environment-prefixed collections in one workspace:

dev_product_events
staging_product_events
prod_product_events

This is simpler for a small team, but the workspace API key can write to every collection in that workspace. Dashboards and queries must also select the correct collection every time.

Use this model when convenience matters more than hard access isolation. Do not rely only on an environment JSON property: a forgotten filter can still mix test and production rows.

Requirement Separate workspaces Separate collections
Distinct API credentials Yes No
Harder to query the wrong environment Yes No
Easy to compare environments Requires switching Yes
Duplicate dashboard setup Usually Not necessarily
Suitable for strict production access Best choice Limited

Centralize routing#

Applications should call one tracking interface rather than constructing GraphJSON requests throughout the codebase.

const allowedEnvironments = new Set(["development", "staging", "production"]);

function collectionFor(environment: string) {
  if (!allowedEnvironments.has(environment)) {
    throw new Error(`Unknown analytics environment: ${environment}`);
  }

  const prefix = {
    development: "dev",
    staging: "staging",
    production: "prod",
  }[environment];

  return `${prefix}_product_events`;
}

Fail closed when configuration is missing. Silently defaulting to production is convenient exactly once.

Your wrapper is also the right place to attach common context, enforce the tracking plan, remove forbidden fields, create an event_id, and queue delivery. Keep deploy-specific values such as the GraphJSON API key and environment name in your secret manager.

Test the event contract#

Instrumentation tests should evaluate the payload, not only whether the tracking function was called.

expect(track).toHaveBeenCalledWith(
  expect.objectContaining({
    event: "subscription_started",
    user_id: "usr_test",
    account_id: "acct_test",
    plan: "pro",
    schema_version: 1,
  })
);

Add tests for the failure cases that matter:

  • required identifiers are missing
  • numeric fields are accidentally sent as strings
  • unknown event or property names are rejected
  • a sensitive field is present
  • a new enum value is not in the tracking plan
  • the production destination is selected in a test process

The tracking-plan template can be the source for generated validators or typed event definitions. Even a hand-maintained allowlist catches spelling drift such as checkout_complete versus checkout_completed.

Run a staging acceptance test#

Before releasing new instrumentation:

  1. Perform the real user action in staging.
  2. Open the collection’s Samples view and find the event.
  3. Confirm the event time, name, IDs, types, and schema_version.
  4. Check that no sensitive or unexpected context was included.
  5. Run the intended SQL query or chart against staging data.
  6. Exercise retries to confirm duplicate events are handled as designed.
  7. Confirm failed deliveries are observable.

A valid HTTP response proves that GraphJSON accepted a request; it does not prove that the event answers the intended product question.

Release with a canary#

For high-volume or business-critical events, release to a small traffic percentage first. Compare the new event against an existing source of truth:

  • checkouts against successful orders
  • signups against newly created users
  • API calls against gateway counters
  • subscription changes against billing records

Compare a complete, settled interval and state the expected difference. Client-side analytics may be lower because of blockers; server-side business events should usually reconcile much more closely.

After release, watch event volume, missing identifiers, unknown enum values, delivery failures, and payload size. Keep the old instrumentation long enough to compare it, but avoid double-counting by giving old and new versions distinct names or schema_version values.

Keep non-production data tidy#

Development and CI can create noisy, high-cardinality values. Use recognizable IDs such as test_e2e_checkout_42, clean up disposable collections, and set shorter retention for non-production streams.

Never run load tests against the production workspace unless the test is explicitly designed, labeled, and excluded from business metrics. For throughput testing, coordinate the expected volume with hi@graphjson.com.

Release checklist#

  • Production and non-production destinations are visibly different.
  • API keys are server-side secrets and scoped by deployment environment.
  • Missing configuration stops delivery instead of defaulting to production.
  • Payload contracts and privacy rules are covered by automated tests.
  • A real staging event has been inspected and queried.
  • A reconciliation metric and acceptable variance are defined.
  • The rollout has a canary and rollback plan.
  • Test data has a short, intentional lifecycle.

Next, use Reliable event delivery to make the production path resilient to traffic spikes and temporary failures.

Need a hand?

Tell us what you’re building and we’ll point you in the right direction.

Contact support