types-testing
TypeScript icon, indicating that this package has built-in type declarations

1.0.0 • Public • Published

types-testing

Test TypeScript types at test runner runtime - Works seamlessly with Jest, Vitest, and Bun.

License NPM Latest NPM Downloads NPM Min Size

CI CodeQL Codecov Type Coverage

  • Types Testing: Ensures TypeScript types or definitions are tested.
  • Readable Tests: Uses the expect-assertion style for clean and easy-to-read types testing.
  • Variative Assertions: Provides assertions like toBe, toBeNever, toBeUnknown, toBeVoid, toBeAny, and more for checking.
  • Framework Agnostic: Seamlessly works with Jest (TypeScript), Vitest, Bun Test, and other TypeScript testing frameworks.
  • Easy Integration: Integrates smoothly into TypeScript testing frameworks.
  • No Special Comments Needed: No need to add special comment like @ts-expect-error, just write clean and readable types tests code inside the existing test suite.
  • Runtime Stack Traces: Offers detailed stack traces for failed tests.

Example Showcases

Here is an example of how types-testing tests TypeScript types or definitions (sources):

// example.test.ts
import { join } from 'node:path';
import { TypesTesting } from 'src';
import exampleModule from './example-module';
import * as ExampleTypes from './example-types';

if (process.versions.bun) {
  let bunTest = (await import('bun:test')).default;

  if (!bunTest) {
    bunTest = await import('bun:test');
  }

  global.describe = bunTest.describe as unknown as typeof global.describe;
  global.test = bunTest.test as unknown as typeof global.test;
} else if (process.env.VITEST) {
  const vitest = await import('vitest');
  global.describe = vitest.describe as unknown as typeof global.describe;
  global.test = vitest.test as unknown as typeof global.test;
}

const basePath = join(process.cwd(), '__tests__/__example');
const { expectType } = new TypesTesting({
  basePath,
  tsConfig: 'tsconfig.json'
}).prepare();

describe('example-module', () => {
  test(`exampleModule.name type is 'example-module'`, () => {
    // using runtime argument
    expectType(exampleModule.name).toBe<'example-module'>();
    expectType(exampleModule.name).toBeStringLiteral();

    // using type argument
    expectType<typeof exampleModule.name>().toBe<'example-module'>();
    expectType<typeof exampleModule.name>().toBeString();
  });

  test(`exampleModule.getName type is function without parameter and return 'example-module'`, () => {
    // using runtime argument
    expectType(exampleModule.getName).toBe<() => 'example-module'>();
    expectType(exampleModule.getName).toBe<{
      (): 'example-module';
    }>();
    expectType(exampleModule.getName()).toBe<'example-module'>();

    // using type argument
    expectType<typeof exampleModule.getName>().toBe<() => 'example-module'>();
    expectType<typeof exampleModule.getName>().toBe<{
      (): 'example-module';
    }>();
    expectType<
      ReturnType<typeof exampleModule.getName>
    >().toBe<'example-module'>();
  });

  if (process.env.FAIL_EXAMPLE_TEST) {
    test('fail example', () => {
      expectType(exampleModule.name).toBe<string>();
    });
  }
});

describe('example-types', () => {
  test('KeyOf is utility types for getting keys from object type', () => {
    expectType<ExampleTypes.KeyOf<{ a: 1; b: 2; c: 3 }>>().toBe<
      'a' | 'b' | 'c'
    >();
  });

  if (process.env.FAIL_EXAMPLE_TEST) {
    test('fail example', () => {
      expectType<ExampleTypes.KeyOf<{ a: 1; b: 2; c: 3 }>>().toBe<1 | 2 | 3>();
    });
  }
});

// example-module.js
export default {
  name: 'example-module',
  getName(){
    return this.name;
  }
};

// example-module.d.ts
declare const exampleModule: {
  name: 'example-module';
  getName(): 'example-module';
}

export default exampleModule;

// example-types.ts
export type KeyOf<T> = T extends T ? keyof T : never;
Result from Jest (29.7.0)
 FAIL  __tests__/__example/example.test.ts
  example-module
    √ exampleModule.name type is 'example-module' (12 ms)
    √ exampleModule.getName type is function without parameter and return 'example-module' (5 ms)
    × fail example (2 ms)
  example-types
    √ KeyOf is utility types for getting keys from object type (1 ms)
    × fail example (1 ms)

  ● example-module › fail example

    TypesTestingError: expectType<received>().toBe<expected>()

    Expected type: string
    Received type: "example-module"


      56 |   if (process.env.FAIL_EXAMPLE_TEST) {
      57 |     test('fail example', () => {
    > 58 |       expectType(exampleModule.name).toBe<string>();
         |                                      ^
      59 |     });
      60 |   }
      61 | });

      at Object.<anonymous> (__tests__/__example/example.test.ts:58:38)

  ● example-types › fail example

    TypesTestingError: expectType<received>().toBe<expected>()

    Expected type: 1 | 2 | 3
    Received type: "a" | "b" | "c"


      70 |   if (process.env.FAIL_EXAMPLE_TEST) {
      71 |     test('fail example', () => {
    > 72 |       expectType<ExampleTypes.KeyOf<{ a: 1; b: 2; c: 3 }>>().toBe<1 | 2 | 3>();
         |                                                              ^
      73 |     });
      74 |   }
      75 | });

      at Object.<anonymous> (__tests__/__example/example.test.ts:72:62)

Test Suites: 1 failed, 1 total
Tests:       2 failed, 3 passed, 5 total
Snapshots:   0 total
Time:        2.336 s, estimated 3 s
Result from Vitest (3.1.1)
 RUN  v3.1.2 /@codeismyid/types-testing

 ❯ __tests__/__example/example.test.ts (5 tests | 2 failed) 12ms
   ✓ example-module > exampleModule.name type is 'example-module' 6ms
   ✓ example-module > exampleModule.getName type is function without parameter and return 'example-module' 2ms
   × example-module > fail example 2ms
     → expectType<received>().toBe<expected>()

Expected type: string
Received type: "example-module"

   ✓ example-types > KeyOf is utility types for getting keys from object type 0ms
   × example-types > fail example 1ms
     → expectType<received>().toBe<expected>()

Expected type: 1 | 2 | 3
Received type: "a" | "b" | "c"


⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 2 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯

 FAIL  __tests__/__example/example.test.ts > example-module > fail example
TypesTestingError: expectType<received>().toBe<expected>()

Expected type: string
Received type: "example-module"

 ❯ __tests__/__example/example.test.ts:55:38
     56|   if (process.env.FAIL_EXAMPLE_TEST) {
     57|     test('fail example', () => {
     58|       expectType(exampleModule.name).toBe<string>();
       |                                      ^
     59|     });
     60|   }

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/2]⎯

 FAIL  __tests__/__example/example.test.ts > example-types > fail example
TypesTestingError: expectType<received>().toBe<expected>()

Expected type: 1 | 2 | 3
Received type: "a" | "b" | "c"

 ❯ __tests__/__example/example.test.ts:69:62
     70|   if (process.env.FAIL_EXAMPLE_TEST) {
     71|     test('fail example', () => {
     72|       expectType<ExampleTypes.KeyOf<{ a: 1; b: 2; c: 3 }>>().toBe<1 | 2 | 3>();
       |                                                              ^
     73|     });
     74|   }

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/2]⎯


 Test Files  1 failed (1)
      Tests  2 failed | 3 passed (5)
   Start at  21:39:24
   Duration  2.78s (transform 185ms, setup 0ms, collect 2.33s, tests 12ms, environment 0ms, prepare 122ms)
Result from Bun Test (1.1.42)
bun test v1.1.42 (50eec002)

__tests__/__example/example.test.ts:
✓ example-module > exampleModule.name type is 'example-module' [16.00ms]
✓ example-module > exampleModule.getName type is function without parameter and return 'example-module' [15.00ms]
53 |     >().toBe<'example-module'>();
54 |   });
55 |
56 |   if (process.env.FAIL_EXAMPLE_TEST) {
57 |     test('fail example', () => {
58 |       expectType(exampleModule.name).toBe<string>();
                                          ^
TypesTestingError: expectType<received>().toBe<expected>()

Expected type: string
Received type: "example-module"

      at /@codeismyid/types-testing/__tests__/__example/example.test.ts:58:38
✗ example-module > fail example [16.00ms]
✓ example-types > KeyOf is utility types for getting keys from object type
67 |     >();
68 |   });
69 |
70 |   if (process.env.FAIL_EXAMPLE_TEST) {
71 |     test('fail example', () => {
72 |       expectType<ExampleTypes.KeyOf<{ a: 1; b: 2; c: 3 }>>().toBe<1 | 2 | 3>();
                                                                  ^
TypesTestingError: expectType<received>().toBe<expected>()

Expected type: 1 | 2 | 3
Received type: "a" | "b" | "c"

      at /@codeismyid/types-testing/__tests__/__example/example.test.ts:72:62
✗ example-types > fail example [16.00ms]

 3 pass
 2 fail
Ran 5 tests across 1 files. [2.09s]

Installation

# NPM
npm install --save-dev types-testing

# BUN
bun add -d types-testing

Usage

Default Options

import { expectType } from 'types-testing';

describe('test', () => {
  test('test', () => {
    expectType<string>().toBe<string>();
  });
});

Note: prepare function get invoked when expectType is called at the first time with process.cwd() as base path and tsconfig.json as TypeScript configuration file.

Custom Typescript Configuration File

import { expectType, prepare } from 'types-testing';

prepare({ basePath: 'this/is/basepath', tsConfig: 'tsconfig.test.json' });

describe('test', () => {
  test('test', () => {
    expectType<string>().toBe<string>();
  });
});

Extending Typescript Configuration File

import { expectType, prepare } from 'types-testing';

prepare({
  basePath: 'this/is/basepath',
  tsConfig: 'tsconfig.test.json',
  compilerOptions: {
    // add ts compiler options to extends typescript compiler options from tsconfig file
  },
  files: [
    // add included files to extends included files from tsconfig file
  ],
  projectReferences: [
    // add project references to extends project references from tsconfig file.
  ]
});

describe('test', () => {
  test('test', () => {
    expectType<string>().toBe<string>();
  });
});

Configuration Without Typescript Configuration File

import { expectType, prepare } from 'types-testing';

prepare({
  compilerOptions: {
    // add ts compiler options
  },
  files: [
    // add included files
  ]
})

describe('test', () => {
  test('test', () => {
    expectType<string>().toBe<string>();
  });
});

Custom Instance

import { TypesTesting } from 'types-testing';

const { expectType, prepare } = new TypesTesting({ 
  autoPrepare: false,
  basePath: process.cwd(), 
  tsConfig: 'tsconfig.test.json' 
});

describe('test', () => {
  beforeAll(()=>{
    prepare();
  });

  test('test', () => {
    expectType<string>().toBe<string>();
  });
});

Developed With

  • Typescript - Strongly typed programming language that builds on JavaScript.
  • Bun - All-in-one JavaScript runtime & toolkit designed for speed, complete with a bundler, test runner, and Node.js-compatible package manager.

License

The code in this project is released under the MIT License.

Package Sidebar

Install

npm i types-testing

Weekly Downloads

65

Version

1.0.0

License

MIT

Unpacked Size

70.7 kB

Total Files

55

Last publish

Collaborators

  • codeismyid