Typorithm · medium

5. API integration and stored postconditions

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

Step 1 of 4 · intro card

5. API integration and stored postconditions

0%

Concept and contract

Exercise the real local HTTP boundary and inspect the effect of a command.

An API integration test should cross a real boundary that a unit fake cannot establish. The companion server supplies a local HTTP contract with synthetic accounts and isolated test namespaces. It is separate from ApplyDjinn production and from third-party vacancy feeds.

Use the request fixture for HTTP observations without loading a browser. Check meaningful payload fields after a successful read, and read stored state after a write. Transport success, status and business effect are different pieces of evidence.

Mechanism and worked example

The worked test signs in the fictional Alice account, sends an idempotent save command and reads the saved list.

The worked test signs in the fictional Alice account, sends an idempotent save command and reads the saved list. It expects one r1 after two identical saves. The API context retains its own cookies, so the later requests share the same synthetic principal.

The request fixture is not automatically the same cookie jar as an unrelated browser context. Use context.request when a browser and request must share state, or transfer authenticated storage deliberately. The authentication lesson examines that distinction directly.

Complete worked example

typescript

import { test, expect } from "../support/test.js";
test("save is idempotent through HTTP", async ({request}) => {
  await request.post("/api/login", {data:{user:"alice"}});
  expect((await request.put("/api/saved/r1")).status()).toBe(204);
  expect((await request.put("/api/saved/r1")).status()).toBe(204);
  const response = await request.get("/api/saved");
  expect(await response.json()).toEqual({ids:["r1"]});
});
Reasoning
The read-back checks both the correct id and absence of duplicates.

Counterexamples and common mistakes

The weak check verifies only that PUT returns 204.

The weak check verifies only that PUT returns 204. It could pass while saving the wrong id or duplicating the same id. A postcondition check makes that possible error observable rather than assuming the response proves the stored effect.

Avoid using real credentials or shared production records in an integration fixture. Scope setup and cleanup to the test namespace. A failed assertion should retain enough nonsecret request context to reproduce the case without exposing tokens.

Counterexample: what this check misses

typescript

import { test, expect } from "../support/test.js";
test("weak write check", async ({request}) => {
  await request.post("/api/login", {data:{user:"alice"}});
  expect((await request.put("/api/saved/r1")).status()).toBe(204);
});
Reasoning
A successful write status can hide the wrong stored effect.

Independent practice: small task

Write a test for the public UA plus qa query and an invalid zero limit.

Write a test for the public UA plus qa query and an invalid zero limit. Expect exact returned ids for the successful query and 400 for the invalid request. Begin with the successful response before adding the rejection.

Optional extension: remove r1 twice after saving it and verify an empty list. Keep each operation’s expected body policy explicit; the save and remove commands return no JSON body.

Step 1: Read the successful query response. Step 2: Add the invalid-limit rejection.

Practice starter: solve before reading the solution

typescript

import { test, expect } from "../support/test.js";
test("catalogue API contract", async ({request}) => {
  // TODO: verify exact matching ids and reject a zero limit.
});
Reasoning
First assert the public subset, then add invalid-input behavior.
  • Hint 1: Use only the loopback lab.
  • Hint 2: Read back a write rather than inferring its effect.

Solution, tests and trade-offs

The solution checks r1 membership and the validation status in separate observations.

The solution checks r1 membership and the validation status in separate observations. It does not parse a 204 response. These tests execute against the actual local server, so route parsing and HTTP status behavior remain real rather than mocked.

Use a fresh namespace for each test. A successful local run proves this contract fixture, not every production dependency or permission combination. Keep the test report’s scope proportional to the exercised boundary.

Practice solution and acceptance checks

typescript

import { test, expect } from "../support/test.js";
test("catalogue API contract", async ({request}) => {
  const response = await request.get("/api/roles?country=UA&q=qa");
  expect(response.status()).toBe(200);
  const data = await response.json();
  expect(data.items.map((role:{id:string}) => role.id)).toEqual(["r1"]);
  expect((await request.get("/api/roles?limit=0")).status()).toBe(400);
});
Reasoning
The HTTP adapter and server are real, while accounts and data remain isolated fixtures.

Interview reasoning and next step

In interviews, describe which parts are real and which data is synthetic.

In interviews, describe which parts are real and which data is synthetic. Explain why a read-back postcondition catches a defect that a status assertion misses. Mention cookie-jar ownership when connecting API setup with a browser test.

Next, test the visible interface with user-facing locators rather than brittle markup positions.

Optional stretch — after the core task works: Delete the same saved id twice and verify the empty list.

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.

Exercise the real local HTTP boundary and inspect the effect of a command.