Type-Safe Table-Driven Test Cases
Define named, typed test cases with duplicate-name checks and run them against an executor, returning pass counts and failures.
Code
export interface TestCase<Input, Expected> {
name: string;
input: Input;
expected: Expected;
}
export interface CaseFailure<Expected> {
name: string;
actual: Expected;
expected: Expected;
}
export interface CaseRunResult<Expected> {
passed: number;
failures: Array<CaseFailure<Expected>>;
}
export function defineCases<Input, Expected>(
cases: ReadonlyArray<TestCase<Input, Expected>>,
): ReadonlyArray<TestCase<Input, Expected>> {
if (!Array.isArray(cases) || cases.length === 0) {
throw new RangeError("cases must be a non-empty array");
}
const names = new Set<string>();
for (const testCase of cases) {
if (typeof testCase.name !== "string" || testCase.name.length === 0) {
throw new TypeError("each case needs a non-empty name");
}
if (names.has(testCase.name)) {
throw new RangeError("duplicate case name: " + testCase.name);
}
names.add(testCase.name);
}
return cases;
}
export function runCases<Input, Expected>(
cases: ReadonlyArray<TestCase<Input, Expected>>,
execute: (input: Input) => Expected,
equals: (actual: Expected, expected: Expected) => boolean = Object.is,
): CaseRunResult<Expected> {
if (typeof execute !== "function") throw new TypeError("execute must be a function");
if (typeof equals !== "function") throw new TypeError("equals must be a function");
let passed = 0;
const failures: Array<CaseFailure<Expected>> = [];
for (const testCase of cases) {
const actual = execute(testCase.input);
if (equals(actual, testCase.expected)) {
passed += 1;
} else {
failures.push({
name: testCase.name,
actual,
expected: testCase.expected,
});
}
}
return { passed, failures };
}Report a problem with this exampleOpens a pre-filled issue on GitHub.