Typorithm · medium

3. Fakes, mocks and a real integration seam

qatestingplaywrightindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

3. Fakes, mocks and a real integration seam

0%

Concept and contract

Control one dependency without proving only that a mock returns its setup.

A test double replaces a dependency to make an observation controlled and repeatable. A fake has a small working behavior, while a mock often records interactions or configured responses. The useful choice depends on the boundary you need to observe, not on a preference for one tool name.

Keep the system under test real. A test that replaces both a repository and the function that should use it may only verify the test’s own setup. At least one integration check should exercise the real adapter against its actual local dependency.

Mechanism and worked example

The worked function asks an injected repository for titles and normalizes them.

The worked function asks an injected repository for titles and normalizes them. The fake supplies deterministic raw values; the assertion checks the real normalization result. This isolates network uncertainty without replacing the behavior the lesson intends to verify.

Interaction checks are useful when the interaction is itself a contract, such as passing the correct account id. Otherwise prefer observable output and postconditions over a brittle count of internal helper calls. Refactoring should not break unrelated implementation-detail assertions.

Complete worked example

typescript

import { test, expect } from "../support/test.js";
async function loadTitles(read: () => Promise<string[]>): Promise<string[]> {
  return (await read()).map(title => title.trim());
}
test("normalizes repository output", async () => {
  expect(await loadTitles(async () => [" QA "])).toEqual(["QA"]);
});
Reasoning
Only the repository is replaced; the normalizer still executes.

Counterexamples and common mistakes

The weak example replaces the entire normalizer with a fixed successful answer and then checks that answer.

The weak example replaces the entire normalizer with a fixed successful answer and then checks that answer. It cannot reveal a trimming defect in the original implementation because that implementation never ran. A configured return value is not evidence of the omitted behavior.

Do not use a fake as proof that a real API still matches its schema. Retain contract or integration tests with representative actual adapter behavior. Clearly label what the double controls and what remains outside the test.

Counterexample: what this check misses

typescript

import { test, expect } from "../support/test.js";
test("weak replacement", async () => {
  const replacedLoader = async () => ["QA"];
  expect(await replacedLoader()).toEqual(["QA"]);
});
Reasoning
Replacing the behavior under test proves only the configured answer.

Independent practice: small task

Implement a small loader that calls an injected repository once and removes blank titles after trimming.

Implement a small loader that calls an injected repository once and removes blank titles after trimming. Supply a fake containing whitespace and a valid title. Start by asserting the normalized list, then add an interaction check only for the promised single read.

Optional extension: exercise the real local HTTP adapter with the same output contract. The basic exercise needs no mocking library and should remain a short, transparent test.

Step 1: Assert the normalized nonblank list. Step 2: Add the promised read-count check.

Practice starter: solve before reading the solution

typescript

import { test, expect } from "../support/test.js";
test("removes blank titles", async () => {
  // TODO: keep the loader real and inject only its repository.
});
Reasoning
Control the raw titles but leave trimming and blank handling real.
  • Hint 1: Replace only the external seam.
  • Hint 2: Keep one real adapter check elsewhere.

Solution, tests and trade-offs

The solution leaves normalization real and controls only the repository.

The solution leaves normalization real and controls only the repository. It records the read count because this exercise explicitly promises one read, not because every internal call count deserves an assertion. Blank values disappear according to the stated contract.

Run a separate integration test for the actual HTTP boundary later. This preserves confidence that the fake and adapter agree without forcing every unit test to start a server.

Practice solution and acceptance checks

typescript

import { test, expect } from "../support/test.js";
async function loadNonblank(read: () => Promise<string[]>): Promise<string[]> {
  return (await read()).map(title => title.trim()).filter(title => title !== "");
}
test("removes blank titles with one read", async () => {
  let reads = 0;
  const fake = async () => { reads++; return [" ", " QA "]; };
  expect(await loadNonblank(fake)).toEqual(["QA"]);
  expect(reads).toBe(1);
});
Reasoning
The output and explicitly promised single read are both observed.

Interview reasoning and next step

In interviews, identify exactly which dependency is replaced and which behavior remains real.

In interviews, identify exactly which dependency is replaced and which behavior remains real. Give one useful interaction assertion and one brittle implementation-detail assertion. Explain the integration evidence that a fake cannot provide.

Next, test properties and transformations with explicit limits, while retaining concrete examples that catch a consistently wrong implementation.

Optional stretch — after the core task works: Run the same contract against the local HTTP adapter.

Computer practice: download the companion lab at https://applydjinn.com/typorithm/qa-lab-20261105.zip. Read its README, install the pinned tools and run the accepted examples before attempting a starter. The mobile reader preserves your reading and self-check progress; the local lab runs browser tests separately.

Control one dependency without proving only that a mock returns its setup.