Q34 of 37 · API testing

How would you build performance tests on top of your existing functional API test framework?

API testingSeniorapiperformancek6load-testingsenior

Short answer

Short answer: Reuse the functional auth, fixtures, and helpers, but call them from a load tool (k6, Locust, JMeter). Treat performance as a separate suite with separate SLAs (p95 < 500ms, error rate < 0.1%). Don't reuse functional assertions — performance tests assert on response time, not response correctness.

Detail

Functional and performance tests share request-shaping logic (auth, payloads) but diverge on assertion intent: functional asserts on correctness, performance on response time and throughput. Building on the functional suite saves duplicate work without conflating concerns.

The reuse boundary:

  • Reuse: auth helpers, fixture builders, request payloads, base URLs.
  • Don't reuse: functional assertions, per-test setup/teardown.

In Playwright + k6:

// shared/payloads.ts — used by both
export const sampleOrderPayload = (suffix: number) => ({
  items: [{ sku: 'SKU-1', qty: 1 }],
  customerEmail: `load-${suffix}@test`,
});
// k6 script
import http from 'k6/http';
import { check } from 'k6';
import { sampleOrderPayload, getToken } from './shared/load.js';

export const options = {
  scenarios: {
    steady: { executor: 'constant-arrival-rate', rate: 100, duration: '5m', preAllocatedVUs: 50 },
  },
  thresholds: {
    http_req_duration: ['p(95)<500', 'p(99)<1000'],
    http_req_failed: ['rate<0.001'],
  },
};

export function setup() {
  return { token: getToken() };
}

export default function (data) {
  const res = http.post(`${__ENV.BASE_URL}/orders`,
    JSON.stringify(sampleOrderPayload(__VU)), {
      headers: {
        Authorization: `Bearer ${data.token}`,
        'Content-Type': 'application/json',
      },
    });
  check(res, { '201': (r) => r.status === 201 });
}

SLAs as test thresholds:

  • p95 latency < 500ms.
  • p99 latency < 1000ms.
  • Error rate < 0.1%.
  • Throughput at target load >= 100 req/s.

If thresholds fail, k6 exits non-zero; CI fails the build. This makes performance regressions detectable.

Test scenarios to run:

  • Steady load — sustained target rate for 5-10 minutes. Catches "fine for 1 minute, dies at 5."
  • Ramp-up — 0 → target over 5 minutes. Catches scale-out / caching cold-start issues.
  • Spike — instant 5x load. Tests rate limits, queue depth, autoscaling response.
  • Soak — target load for 1 hour. Catches memory leaks, connection pool exhaustion.
  • Stress — increase load until failure. Tells you the actual ceiling.

What to monitor while tests run:

  • Server-side metrics (CPU, memory, DB connections, queue depth).
  • Database query times, slow query log.
  • Cache hit rates.
  • Tail latency (p99.9, max) — averages hide everything important.

Where to run:

  • Dedicated perf environment, sized like production.
  • Don't run perf tests against shared staging — you'll affect other tests.
  • Don't run against prod unless you have a clear plan and capacity headroom.

Cadence:

  • Smoke perf test (1 minute, low load) on PR — catches obvious regressions.
  • Full perf suite weekly or before release.
  • Soak tests monthly.

Anti-patterns:

  • Asserting on individual response correctness in load tests. The point is throughput; correctness is the functional suite's job.
  • Running perf tests with too few virtual users — bottlenecks shift. 1 user shows server CPU; 100 users show queueing.
  • Treating perf as one-off ("we did a perf test in Q1"). Performance regresses silently; periodic re-runs are non-negotiable.

The senior signal: reusing functional building blocks, separating concerns (functional = correctness; perf = throughput), and treating thresholds as build-blocking.

// WHAT INTERVIEWERS LOOK FOR

Sharing helpers but separating assertion intent, threshold-based test failure, multiple scenarios (steady, ramp, spike, soak), and tail-latency awareness over averages.

// COMMON PITFALL

Asserting on response correctness in load tests, then chasing per-request 'errors' that aren't the perf signal. Functional and perf suites should not duplicate each other's assertions.