Item 77: Understand the Relationship Between Type Checking and Unit Testing
May 10, 2024 ยท View on GitHub
Things to Remember
- Type checking and unit testing are different, complementary techniques for demonstrating program correctness. You want both.
- Unit tests demonstrate correct behavior on particular inputs, while type checking eliminates whole classes of incorrect behaviors.
- Rely on the type checker to check types. Write unit tests for behaviors that can't be checked with types.
- Avoid testing inputs that would be type errors unless there are concerns about security or data corruption.
Code Samples
test('add', () => {
expect(add(0, 0)).toEqual(0);
expect(add(123, 456)).toEqual(579);
expect(add(-100, 90)).toEqual(-10);
});
function add(a: number, b: number): number {
if (isNaN(a) || isNaN(b)) {
return 'Not a number!';
// ~~~ Type 'string' is not assignable to type 'number'.
}
return (a|0) + (b|0);
}
function add(a: number, b: number): number {
return a - b; // oops!
}
test('out-of-domain add', () => {
expect(add(null, null)).toEqual(0);
// ~~~~ Type 'null' is not assignable to parameter of type 'number'.
expect(add(null, 12)).toEqual(12);
// ~~~~ Type 'null' is not assignable to parameter of type 'number'.
expect(add(undefined, null)).toBe(NaN);
// ~~~~~~~~~ Type 'undefined' is not assignable to parameter of ...
expect(add('ab', 'cd')).toEqual('abcd');
// ~~~~ Type 'string' is not assignable to parameter of type 'number'.
});
interface User {
id: string;
name: string;
memberSince: string;
}
declare function updateUserById(
id: string,
update: Partial<Omit<User, 'id'>> & {id?: never}
): Promise<User>;
test('invalid update', () => {
// @ts-expect-error Can't call updateUserById to update an ID.
expect(() => updateUserById('123', {id: '234'})).toReject();
});