Skip to main content
Sep 17, 2026service virtualization load testing

By Lucas Yoris · Performate

Mock Services vs Real Dependencies During Load Tests: A Pragmatic Middle Ground

Mocks vs real deps in load tests: WireMock-style virtualization risks, fidelity gaps, and when to hit sandboxes—honest performance evidence.

Your WireMock cluster returns 50ms forever—while the vendor sandbox wobbles between 200ms and 2s, and production adds OAuth refresh and quota headers your stub never implemented. Service virtualization accelerates schedules but hides real latency tails unless you document fidelity gaps beside every chart.

Mocks vs real dependencies is not a purity debate. It is a risk allocation question: what evidence do you need before launch, and which lies are acceptable in CI versus forbidden in release sign-off? In this guide you will learn a pragmatic middle ground—contract mocks for speed, sandbox traffic for vendor truth, and latency injection for full-stack soak—without fork explosions or finance-unfriendly surprises.

Why mocks lie in helpful ways

Virtual services (WireMock overview) stabilize functional tests and unblock teams when sandboxes are down. Under load, they often remove the behavior you need to measure:

  • Fixed latency hides tail spikes, cold starts, and retry storms from real APIs (third-party APIs).
  • Simplified auth skips token expiry, mTLS, and per-tenant quota headers.
  • Happy-path-only responses mask circuit-breaker and degradation paths production will hit.
  • Infinite capacity lets your service look scalable while downstream would 429 first.
  • Schema parity ≠ timing parity—contract tests pass while performance evidence is hollow.

Document fidelity gaps openly—finance and product read honesty (staging vs prod).

When mock-only load tests pass and launches fail

Green k6 summaries against stubs prove your service handles its own concurrency—not that the full chain survives vendor RPM limits or regional latency. Canary traffic against contractual sandbox limits closes part of that gap; full-stack soak with injected downstream delay closes another.

Practical k6 implementation: dual-track scenarios

Keep mock and integrated paths in one script with env-driven base URLs and tags—dep:mock vs dep:sandbox—so reports compare tracks without duplicated logic.

Example script (illustrative—not a production-ready test). Uses fictional URLs and SLO numbers. Adapt dependency hosts, auth, and thresholds to your architecture.

What this example demonstrates:

  • Env switch: PAYMENTS_URL points to mock or sandbox without forking scripts.
  • Parallel scenarios: steady load on mock for CI; lower-rate sandbox track for scheduled runs.
  • Latency injection header on mock path to simulate downstream delay when stub is instant.
  • Tagged metrics per dependency mode for side-by-side threshold review.
  • Stricter error thresholds on sandbox where flakiness is informational, not ignored.
import http from 'k6/http';
import { check, sleep } from 'k6';

const CHECKOUT = __ENV.API_BASE || 'https://staging.example.com';
const PAYMENTS = __ENV.PAYMENTS_URL || 'https://payments-mock.internal';
const MODE = __ENV.DEP_MODE || 'mock'; // mock | sandbox

export const options = {
  scenarios: {
    checkout_mock: {
      executor: 'constant-arrival-rate',
      rate: Number(__ENV.MOCK_RPS || 20),
      timeUnit: '1s',
      duration: '3m',
      preAllocatedVUs: 15,
      maxVUs: 60,
      exec: 'checkout',
      startTime: '0s',
      env: { DEP_MODE: 'mock' },
      tags: { dep: 'mock', route: 'checkout' },
    },
    checkout_sandbox: {
      executor: 'constant-arrival-rate',
      rate: Number(__ENV.SANDBOX_RPS || 3),
      timeUnit: '1s',
      duration: '3m',
      preAllocatedVUs: 5,
      maxVUs: 20,
      exec: 'checkout',
      startTime: '0s',
      env: { DEP_MODE: 'sandbox' },
      tags: { dep: 'sandbox', route: 'checkout' },
    },
  },
  thresholds: {
    'http_req_duration{dep:mock}': ['p(95)<600'],
    'http_req_duration{dep:sandbox}': ['p(95)<2500'],
    'http_req_failed{dep:sandbox}': ['rate<0.05'],
  },
};

export function checkout() {
  const mode = __ENV.DEP_MODE || MODE;
  const body = JSON.stringify({
    amount: 4999,
    currency: 'USD',
    paymentsBase: PAYMENTS,
  });

  const headers = {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${__ENV.API_TOKEN}`,
    'X-Load-Dep-Mode': mode,
  };

  // Optional: ask mock layer to inject downstream delay for fuller stack shape
  if (mode === 'mock') {
    headers['X-Mock-Delay-Ms'] = __ENV.MOCK_DELAY_MS || '150';
  }

  const res = http.post(`${CHECKOUT}/checkout`, body, {
    headers,
    tags: { dep: mode, route: 'checkout' },
  });

  check(res, {
    'checkout 2xx': (r) => r.status >= 200 && r.status < 300,
  });
  sleep(0.2);
}

Patterns that work

  • Contract mocks for functional parity and fast CI smoke—tight thresholds, low rate.
  • Sandbox vendor traffic within contractual RPM during scheduled windows—tag and threshold separately.
  • Latency injection on mocks before launch—synthetic but realistic downstream delay distributions.
  • Dual-track tags in one repo—avoid checkout-mock.js and checkout-real.js drift.
  • Explicit fidelity notes in run README: what mock omits (429s, OAuth refresh, regional variance).

Anti-patterns to avoid

  • Launch sign-off from mock-only soak tests without sandbox validation.
  • Hitting production vendor endpoints in load tests without contract and rate approval.
  • Ignoring that sandbox behavior differs from production—document the gap, do not hide it.
  • Duplicating entire scenario trees per dependency—parameterize URLs and tags instead.

Pro tip (example commands): same script, two evidence tracks.

# CI — fast mock track
k6 run checkout-deps.js -e DEP_MODE=mock -e MOCK_RPS=15 -e MOCK_DELAY_MS=120

# Nightly — sandbox within vendor RPM cap
k6 run checkout-deps.js -e DEP_MODE=sandbox \
  -e PAYMENTS_URL=https://sandbox.payments-vendor.example.com \
  -e SANDBOX_RPS=2

What these commands demonstrate: dependency mode is runtime input; sandbox runs stay low-rate by design.

Decision framework: mock vs sandbox vs injected latency

SituationRecommended action
PR smoke / CI budgetMock dependencies; optional fixed delay injection
Vendor SLA validationSandbox at contractual RPM; tag dep:sandbox
Pre-launch soakFull-stack with latency injection + limited sandbox mix
Third-party outage behaviorSandbox or recorded fault injection—not infinite mock success
Contract schema driftContract tests in CI; perf on mock only after schemas align
Production-like sign-offNever mock-only; pair with staging vs prod guidance

Use mocks if you need deterministic, fast feedback on your service's own logic and concurrency.

Use vendor sandboxes if quota, auth, and tail latency are release risks—and contracts permit load.

Use latency injection if mocks are instant but you need soak validation of timeouts, thread pools, and circuit breakers before launch.

Observability and honesty checklist

Before stakeholders read charts:

  • Label each run with dep:mock or dep:sandbox in report title and summary JSON.
  • Document known fidelity gaps (429 behavior, auth, regional latency) beside results.
  • Compare mock vs sandbox tails explicitly—do not merge into one green line.
  • Archive RPM limits and vendor ticket numbers for sandbox windows.
  • Automate mock-track smoke in CI; schedule sandbox tracks separately.

How Performate keeps dual-track scenarios organized

Fork explosions happen when mock and integrated paths live in separate repos. Below is a concrete workflow example for the checkout + payments dependency this article uses.

Example: mock CI smoke and sandbox nightly in one workspace

  1. Import checkout collection with payments dependency parameterized as collection variable paymentsBase. Problem solved: one request definition; swap target per profile.
  2. Create ci_mock environment profile pointing paymentsBase at internal WireMock URL; set low arrival rate. Problem solved: fast PR gate without vendor spend.
  3. Create nightly_sandbox profile with vendor sandbox URL and RPM-capped rate. Problem solved: scheduled truth without editing executor code by hand.
  4. Tag scenarios dep:mock and dep:sandbox for filtered reports. Problem solved: side-by-side comparison in one export.
  5. Add mock delay parameter in scenario headers or pre-request script equivalent before export. Problem solved: inject downstream latency without abandoning mocks entirely.
  6. Export k6 script with __ENV.PAYMENTS_URL and DEP_MODE for pipeline parity. Problem solved: UI iteration and CI/nightly jobs stay aligned.

That workflow maps directly to the cta in this post: simplify k6 load testing from imports to results so teams ship performance confidence faster—without pretending mocks are production.

Closing takeaway

Service virtualization wins on speed; sandboxes and injected latency win on evidence. Use both deliberately, tag every run with dependency mode, and document what the mock never simulated.

Run your checkout flow on mock for CI this week, schedule a low-rate sandbox pass before the next release review, and record fidelity gaps next to the charts finance will read.

Try Performate free | Book a demo | k6 scenarios

Ready to optimize your API performance?

Explore how Performate simplifies k6 load testing—from imports to results—so your team ships performance confidence faster.

← Back to all posts