Typorithm · medium

1. A test runner and assertions that can fail

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

Step 1 of 4 · intro card

1. A test runner and assertions that can fail

0%

Concept and contract

Run a small test with an independently chosen expected result.

A test runner discovers cases, supplies fixtures and reports failures. An assertion compares an observation with an expectation. A green run is meaningful only if the expectation would reject a relevant wrong result; a test containing no useful assertion can pass while the feature is broken.

Use the downloadable local lab and its pinned dependencies. Run npm ci, npm run typecheck and npm test separately. Playwright transforms TypeScript for execution but does not replace a TypeScript type check. Keep that distinction visible from the first lesson.

Mechanism and worked example

The worked case calls a pure filter and compares exact ids with the hand-written fixture expectation.

The worked case calls a pure filter and compares exact ids with the hand-written fixture expectation. It needs no browser because this particular contract is a deterministic function. The same runner can also supply a browser or API context in later lessons.

Arrange a known input, act through the boundary being tested and assert an observable result. This structure is a readability aid, not a requirement to add comments around every line. Keep the first test short enough to explain completely.

Complete worked example

typescript

import { test, expect } from "../support/test.js";
import { roles, selectRoles, validInteger } from "../support/domain.mjs";
test("UA query has one expected role", () => {
  const result = selectRoles(roles, "UA", "qa");
  expect(result.map(role => role.id)).toEqual(["r1"]);
});
Reasoning
The expected ids are specified independently of the function call.

Counterexamples and common mistakes

The weak check compares the result with itself.

The weak check compares the result with itself. It passes for an empty array or the wrong records because both sides repeat the same observation. This is not an independent oracle, even though the assertion library reports success.

Avoid catching every assertion failure and continuing as if the test passed. The runner must receive a failure when the contract is violated. Diagnostic logging can help, but it must not swallow the outcome.

Counterexample: what this check misses

typescript

import { test, expect } from "../support/test.js";
import { roles, selectRoles, validInteger } from "../support/domain.mjs";
test("weak self comparison", () => {
  const result = selectRoles(roles, "UA", "qa");
  expect(result).toEqual(result);
});
Reasoning
Self-comparison cannot distinguish a correct result from an incorrect one.

Independent practice: small task

Write a test for country US with an empty query.

Write a test for country US with an empty query. Expect r2 and r4 in order. Begin by listing those ids before calling the filter, then add the assertion. Use the same fixture without mutating it.

Run the test, then temporarily replace the result with an empty array and confirm it fails. This deliberate local check develops trust in the test. It does not require a sophisticated mocking framework or a production environment.

Step 1: Write r2 and r4 as the expected ids. Step 2: Call the filter and compare the ordered result.

Practice starter: solve before reading the solution

typescript

import { test, expect } from "../support/test.js";
import { roles, selectRoles, validInteger } from "../support/domain.mjs";
test("US fixture ids", () => {
  // TODO: act and compare with an independent expected list.
});
Reasoning
Write the expected US ids before invoking the function.
  • Hint 1: Do not derive expected ids with the same predicate.
  • Hint 2: Let assertion failures reach the runner.

Solution, tests and trade-offs

The solution compares the selected ids with a literal expected array.

The solution compares the selected ids with a literal expected array. A wrong empty or overbroad result fails. The verifier executes the worked and solution cases and runs a separate controlled mutation so a green result is backed by a demonstrated failure signal.

Restore the correct implementation after the experiment. Keep failure evidence separate from the successful verification report so the intentionally red run is not confused with an unresolved regression.

Practice solution and acceptance checks

typescript

import { test, expect } from "../support/test.js";
import { roles, selectRoles, validInteger } from "../support/domain.mjs";
test("US fixture ids", () => {
  const result = selectRoles(roles, "US", "");
  expect(result.map(role => role.id)).toEqual(["r2", "r4"]);
});
Reasoning
The solution fails for empty, extra or reordered results.

Interview reasoning and next step

In interviews, explain the independent oracle and show one wrong implementation the test rejects.

In interviews, explain the independent oracle and show one wrong implementation the test rejects. Distinguish compiler checking from executing assertions. A small credible test is better evidence than a large file of unconditional passes.

Next, choose data around behavior boundaries and parameterize only the repetition that makes those cases clearer.

Optional stretch — after the core task works: Try an empty-result mutation and preserve its failure evidence.

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.

Run a small test with an independently chosen expected result.