Skip to content

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)
})

💻 playground


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)
}

💻 playground


ts
function add(a: number, b: number): number {
  return a - b // oops!
}

💻 playground


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'.
})

💻 playground


ts
interface User {
  id: string
  name: string
  memberSince: string
}

declare function updateUserById(
  id: string,
  update: Partial<Omit<User, 'id'>> & { id?: never }
): Promise<User>

💻 playground


ts
test('invalid update', () => {
  // @ts-expect-error Can't call updateUserById to update an ID.
  expect(() => updateUserById('123', { id: '234' })).toReject()
})

💻 playground

Released under the MIT License.