Skip to content

Latest commit

 

History

History
293 lines (215 loc) · 14.8 KB

test-your-types.md

File metadata and controls

293 lines (215 loc) · 14.8 KB

Item 55: Write Tests for Your Types

Things to Remember

  • When testing types, be aware of the difference between equality and assignability, particularly for function types.
  • For functions that use callbacks, test the inferred types of the callback parameters. Don't forget to test the type of this if it's part of your API.
  • Avoid writing your own type testing code. Use one of the standard tools instead.
  • For code on DefinitelyTyped, use dtslint. For your own code, use vitest, expect-type, or the Type Challenges approach. If you want to test type display, use eslint-plugin-expect-type.

Code Samples

declare function map<U, V>(array: U[], fn: (u: U) => V): V[];

💻 playground


map(['2017', '2018', '2019'], v => Number(v));

💻 playground


test('square a number', () => {
  square(1);
  square(2);
});

💻 playground


const lengths: number[] = map(['john', 'paul'], name => name.length);

💻 playground


function assertType<T>(x: T) {}

assertType<number[]>(map(['john', 'paul'], name => name.length));

💻 playground


const n = 12;
assertType<number>(n);  // OK

💻 playground


const beatles = ['john', 'paul', 'george', 'ringo'];
assertType<{name: string}[]>(
  map(beatles, name => ({
    name,
    inYellowSubmarine: name === 'ringo'
  }))
);  // OK

💻 playground


const add = (a: number, b: number) => a + b;
assertType<(a: number, b: number) => number>(add);  // OK

const double = (x: number) => 2 * x;
assertType<(a: number, b: number) => number>(double);  // OK!?

💻 playground


const g: (x: string) => any = () => 12;  // OK

💻 playground


const double = (x: number) => 2 * x;
declare let p: Parameters<typeof double>;
assertType<[number, number]>(p);
//                           ~ Argument of type '[number]' is not
//                             assignable to parameter of type [number, number]
declare let r: ReturnType<typeof double>;
assertType<number>(r);  // OK

💻 playground


const beatles = ['john', 'paul', 'george', 'ringo'];
assertType<number[]>(map(
  beatles,
  function(name, i, array) {
    // ~~~ Argument of type '(name: any, i: any, array: any) => any' is
    //     not assignable to parameter of type '(u: string) => any'
    assertType<string>(name);
    assertType<number>(i);
    assertType<string[]>(array);
    assertType<string[]>(this);
    //                   ~~~~ 'this' implicitly has type 'any'
    return name.length;
  }
));

💻 playground


declare function map<U, V>(
  array: U[],
  fn: (this: U[], u: U, i: number, array: U[]) => V
): V[];

💻 playground


declare module 'your-amazing-module';

💻 playground


// @ts-expect-error only takes two parameters
map([1, 2, 3], x => x * x, 'third parameter');

💻 playground


declare const map: any;
map([1, 2, 3], x => x * x, 'third parameter');
//             ~ Parameter 'x' implicitly has an 'any' type.

💻 playground


map(
  [1, 2, 3],
  x => x * x,
  // @ts-expect-error only takes two parameters
  'third parameter'
);

💻 playground


import {expectTypeOf} from 'expect-type';

const beatles = ['john', 'paul', 'george', 'ringo'];
expectTypeOf(map(
  beatles,
  function(name, i, array) {
    expectTypeOf(name).toEqualTypeOf<string>();
    expectTypeOf(i).toEqualTypeOf<number>();
    expectTypeOf(array).toEqualTypeOf<string[]>();
    expectTypeOf(this).toEqualTypeOf<string[]>();
    return name.length;
  }
)).toEqualTypeOf<number[]>();

💻 playground


const anyVal: any = 1;
expectTypeOf(anyVal).toEqualTypeOf<number>();
//                                 ~~~~~~
//           Type 'number' does not satisfy the constraint 'never'.

const double = (x: number) => 2 * x;
expectTypeOf(double).toEqualTypeOf<(a: number, b: number) => number>();
//                                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//           Type ... does not satisfy '"Expected: function, Actual: never"'

interface ABReadOnly {
  readonly a: string;
  b: number;
}
declare let ab: {a: string, b: number};
expectTypeOf(ab).toEqualTypeOf<ABReadOnly>();
//               ~~~~~~~~~~~~~
//           Arguments for the rest parameter 'MISMATCH' were not provided.
expectTypeOf(ab).toEqualTypeOf<{a: string, b: number}>();  // OK

💻 playground


export type Equals<X, Y> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? true : false;

export type Expect<T extends true> = T;

const double = (x: number) => 2 * x;
type Test1 = Expect<Equals<typeof double, (x: number) => number>>;
type Test2 = Expect<Equals<typeof double, (x: string) => number>>;
//                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//                  Type 'false' does not satisfy the constraint 'true'.

💻 playground


type Test3 = Expect<Equals<1 | 2, 2 | 1>>;  // good!
type Test4 = Expect<Equals<[a: 1, b: 2], [1, 2]>>;  // maybe not so good
type Test5 = Expect<Equals<{x: 1} & {y: 2}, {x: 1, y: 2}>>;  // surprising
//                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//                  Type 'false' does not satisfy the constraint 'true'.

💻 playground


const beatles = ['john', 'paul', 'george', 'ringo'];
map(beatles, function(
  name,  // $ExpectType string
  i,     // $ExpectType number
  array  // $ExpectType string[]
) {
  this   // $ExpectType string[]
  return name.length;
});  // $ExpectType number[]

💻 playground


const spiceGirls = ['scary', 'sporty', 'baby', 'ginger', 'posh'];
//    ^? const spiceGirls: string[]

💻 playground


type Game = 'wordle' | 'crossword' | (string & {});
const spellingBee: Game = 'spelling bee';
let g: Game = '';

💻 playground