Harness-engineering ts-testing-types

TypeScript Testing Types

install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/ts-testing-types" ~/.claude/skills/intense-visions-harness-engineering-ts-testing-types-58728a && rm -rf "$T"
manifest: agents/skills/codex/ts-testing-types/SKILL.md
source content

TypeScript Testing Types

Test TypeScript types at compile time using expect-type, tsd, and vitest type matchers

When to Use

  • Verifying that utility types produce the expected output types
  • Testing that generic functions infer types correctly
  • Ensuring library public APIs maintain correct type signatures
  • Catching type regressions in CI without runtime tests

Instructions

  1. Use
    expectTypeOf
    from vitest
    (built-in, no extra dependencies):
import { expectTypeOf, test } from 'vitest';

test('User type has correct shape', () => {
  expectTypeOf<User>().toMatchTypeOf<{
    id: string;
    name: string;
    email: string;
  }>();
});
  1. Test function return types:
test('createUser returns User', () => {
  expectTypeOf(createUser).returns.toMatchTypeOf<User>();
  expectTypeOf(createUser).parameters.toEqualTypeOf<[name: string, email: string]>();
});
  1. Test generic inference:
test('identity preserves literal types', () => {
  const result = identity('hello');
  expectTypeOf(result).toEqualTypeOf<'hello'>();
});
  1. Verify types are NOT assignable:
test('UserId and PostId are not interchangeable', () => {
  expectTypeOf<UserId>().not.toEqualTypeOf<PostId>();
  expectTypeOf<UserId>().not.toMatchTypeOf<PostId>();
});
  1. Use
    expect-type
    library
    for standalone type tests:
import { expectTypeOf } from 'expect-type';

// In a .test-d.ts file or regular test file
expectTypeOf<Pick<User, 'id' | 'name'>>().toEqualTypeOf<{
  id: string;
  name: string;
}>();

expectTypeOf<Partial<User>>().toMatchTypeOf<{ id?: string }>();
  1. Use
    tsd
    for declaration file testing:
// test-d.ts
import { expectType, expectError } from 'tsd';
import { createUser } from './index';

expectType<User>(createUser('Alice', 'alice@test.com'));
expectError(createUser(123)); // Should fail with number input

Run with

npx tsd
in package.json.

  1. Test conditional types:
test('UnwrapPromise extracts inner type', () => {
  expectTypeOf<UnwrapPromise<Promise<string>>>().toEqualTypeOf<string>();
  expectTypeOf<UnwrapPromise<Promise<Promise<number>>>>().toEqualTypeOf<number>();
  expectTypeOf<UnwrapPromise<string>>().toEqualTypeOf<string>();
});
  1. Test mapped types:
test('Nullable adds null to all properties', () => {
  type Input = { name: string; age: number };
  type Expected = { name: string | null; age: number | null };
  expectTypeOf<Nullable<Input>>().toEqualTypeOf<Expected>();
});
  1. Compile-time only assertions (no runtime, no test runner):
// types.test.ts — checked by tsc, not executed
type Assert<T extends true> = T;
type IsEqual<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;

type _test1 = Assert<IsEqual<ReturnType<typeof getUser>, Promise<User>>>;
type _test2 = Assert<IsEqual<Parameters<typeof createUser>, [string, string]>>;

Details

Type tests verify that your type-level code (generics, utility types, conditional types) produces the expected types. They catch regressions that runtime tests cannot — a function might work correctly at runtime but accept the wrong types due to an overly permissive generic.

toEqualTypeOf
vs
toMatchTypeOf
:

  • toEqualTypeOf
    — exact match.
    { a: string }
    does NOT equal
    { a: string; b?: number }
  • toMatchTypeOf
    — structural compatibility.
    { a: string; b: number }
    MATCHES
    { a: string }

Tool comparison:

  • Vitest
    expectTypeOf
    — integrated into vitest, runs alongside runtime tests, best for projects already using vitest
  • expect-type
    — standalone library, works with any test runner, same API as vitest's built-in
  • tsd
    — designed for library authors testing .d.ts files, uses a different assertion API

When to test types:

  • Utility types that transform other types (DeepPartial, NonNullable variants)
  • Generic functions with complex inference
  • Public API surfaces of libraries
  • Discriminated union handlers (ensure exhaustiveness)

When NOT to test types:

  • Simple type annotations (TypeScript already checks these)
  • One-off types used in a single file
  • Types that mirror a runtime test's assertions

Trade-offs:

  • Type tests add maintenance overhead — but catch regressions that runtime tests miss
  • expectTypeOf
    assertions run at compile time, not runtime — test files must be type-checked by tsc
  • Complex type assertions can produce cryptic error messages
  • Type tests do not verify runtime behavior — always pair with runtime tests for critical paths

Source

https://typescriptlang.org/docs/handbook/2/types-from-types.html

Process

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. Verify your implementation against the details and edge cases listed above.

Harness Integration

  • Type: knowledge — this skill is a reference document, not a procedural workflow.
  • No tools or state — consumed as context by other skills and agents.

Success Criteria

  • The patterns described in this document are applied correctly in the implementation.
  • Edge cases and anti-patterns listed in this document are avoided.