---
title: "Mock Services vs Real Dependencies During Load Tests: A Pragmatic Middle Ground"
description: "Mocks vs real deps in load tests: WireMock-style virtualization risks, fidelity gaps, and when to hit sandboxes—honest performance evidence."
publishDate: "2026-09-17"
draft: false
keyword: "service virtualization load testing"
intent: "TOFU"
cta: "Explore how Performate simplifies k6 load testing—from imports to results—so your team ships performance confidence faster."
tags: ["k6","load-testing","api-performance","service","virtualization"]
---

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](https://wiremock.org/docs/)) 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](/en/blog/third-party-api-dependencies-load-testing)).
- **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](/en/blog/staging-vs-production-load-tests)).

### 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.

```javascript
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.

```bash
# 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

| Situation | Recommended action |
|:---|:---|
| PR smoke / CI budget | Mock dependencies; optional fixed delay injection |
| Vendor SLA validation | Sandbox at contractual RPM; tag `dep:sandbox` |
| Pre-launch soak | Full-stack with latency injection + limited sandbox mix |
| Third-party outage behavior | Sandbox or recorded fault injection—not infinite mock success |
| Contract schema drift | Contract tests in CI; perf on mock only after schemas align |
| Production-like sign-off | Never mock-only; pair with [staging vs prod guidance](/en/blog/staging-vs-production-load-tests) |

**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](https://performate.app) | [Book a demo](/demo) | [k6 scenarios](https://k6.io/docs/using-k6/scenarios/)
