Typorithm · medium

4. Property and metamorphic checks with concrete anchors

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

Step 1 of 4 · intro card

4. Property and metamorphic checks with concrete anchors

0%

Concept and contract

Check relationships across inputs without losing an independent example oracle.

A property states a relationship that should hold across a defined input domain. A metamorphic check compares related inputs when a complete output oracle is inconvenient. For example, normalizing a query twice should not change its meaning, and adding an unrelated role should not change a specific filtered result.

State the domain and limits. A bounded deterministic set is a useful learning start; a property-testing library can later generate and shrink inputs. Record seeds and counterexamples when randomness is introduced so a failure remains reproducible.

Mechanism and worked example

The worked test compares a trimmed mixed-case query with its normalized equivalent.

The worked test compares a trimmed mixed-case query with its normalized equivalent. It also keeps a concrete expected r1 result. The relationship checks consistency, while the concrete anchor prevents an always-empty implementation from passing both sides.

A property is only as useful as its ability to reject relevant wrong behavior. Idempotence alone can hold for a function that always returns nothing. Combine complementary observations instead of treating a mathematical-looking assertion as automatic completeness.

Complete worked example

typescript

import { test, expect } from "../support/test.js";
import { roles, selectRoles, validInteger } from "../support/domain.mjs";
test("normalization preserves the anchored result", () => {
  const original = selectRoles(roles, "UA", " QA ").map(role => role.id);
  const normalized = selectRoles(roles, "UA", "qa").map(role => role.id);
  expect(original).toEqual(["r1"]);
  expect(normalized).toEqual(original);
});
Reasoning
A concrete id anchor complements the normalization relationship.

Counterexamples and common mistakes

The weak test checks that filtering the same input twice gives the same answer.

The weak test checks that filtering the same input twice gives the same answer. A deterministic but incorrect implementation passes it. Repetition can help detect nondeterminism, but it does not establish the required membership or filter semantics.

Avoid huge generated workloads before a simple failing example is understood. Keep early cases bounded and explainable. Shrinking is useful because the smallest counterexample often makes the violated assumption clearer than a large random record.

Counterexample: what this check misses

typescript

import { test, expect } from "../support/test.js";
import { roles, selectRoles, validInteger } from "../support/domain.mjs";
test("weak repeatability", () => {
  expect(selectRoles(roles)).toEqual(selectRoles(roles));
});
Reasoning
A deterministic wrong function can pass self-repeatability.

Independent practice: small task

Add an unrelated US designer to the fixture and verify that UA plus qa still returns r1.

Add an unrelated US designer to the fixture and verify that UA plus qa still returns r1. Keep a separate concrete assertion for the original fixture. Begin by explaining why the new record should not affect this query.

Optional extension: generate several unrelated records with a recorded seed. The basic task uses one hand-written transformation so you can reason about its expected effect without learning a generator API first.

Step 1: Explain why the new record is unrelated. Step 2: Compare transformed results and retain r1 as a concrete anchor.

Practice starter: solve before reading the solution

typescript

import { test, expect } from "../support/test.js";
import { roles, selectRoles, validInteger } from "../support/domain.mjs";
test("unrelated data does not change this query", () => {
  // TODO: extend a copy, compare results and keep a concrete anchor.
});
Reasoning
Explain why the added record is unrelated before writing the invariant.
  • Hint 1: A relationship alone can admit consistently wrong behavior.
  • Hint 2: Keep early generated domains small.

Solution, tests and trade-offs

The solution compares original and extended fixture results and anchors both to r1.

The solution compares original and extended fixture results and anchors both to r1. The added role matches neither the country nor the query. This rejects unrelated-data contamination while avoiding a claim that every possible new record must leave every query unchanged.

The verifier also tries an always-empty mutant and confirms the concrete anchor fails. Relationship checks and example checks provide different evidence; neither should silently replace the other.

Practice solution and acceptance checks

typescript

import { test, expect } from "../support/test.js";
import { roles, selectRoles, validInteger } from "../support/domain.mjs";
test("unrelated data does not change this query", () => {
  const extra = {id:"r5",title:"Designer",country:"US" as const,remote:false,company:"C"};
  const before = selectRoles(roles, "UA", "qa").map(role => role.id);
  const after = selectRoles([...roles, extra], "UA", "qa").map(role => role.id);
  expect(before).toEqual(["r1"]);
  expect(after).toEqual(before);
});
Reasoning
The concrete anchor rejects an always-empty function that a relationship alone might miss.

Interview reasoning and next step

In interviews, name the domain, transformation and invariant, then show a wrong function that would pass a weaker property.

In interviews, name the domain, transformation and invariant, then show a wrong function that would pass a weaker property. Describe how a seed and smaller counterexample support diagnosis. Avoid claiming a generated case count is proof of universal correctness.

Next, cross the real local API boundary and inspect both response data and stored postconditions.

Optional stretch — after the core task works: Add seeded generated unrelated records and preserve a counterexample.

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.

Check relationships across inputs without losing an independent example oracle.