Item 77: Understand the Relationship Between Type Checking and Unit Testing
要点
- 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.
正文
ts
test('add', () => {
expect(add(0, 0)).toEqual(0)
expect(add(123, 456)).toEqual(579)
expect(add(-100, 90)).toEqual(-10)
})
ts
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)
}
ts
function add(a: number, b: number): number {
return a - b // oops!
}
ts
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'.
})
ts
interface User {
id: string
name: string
memberSince: string
}
declare function updateUserById(
id: string,
update: Partial<Omit<User, 'id'>> & { id?: never }
): Promise<User>
ts
test('invalid update', () => {
// @ts-expect-error Can't call updateUserById to update an ID.
expect(() => updateUserById('123', { id: '234' })).toReject()
})