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.mdsource 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
- Use
from vitest (built-in, no extra dependencies):expectTypeOf
import { expectTypeOf, test } from 'vitest'; test('User type has correct shape', () => { expectTypeOf<User>().toMatchTypeOf<{ id: string; name: string; email: string; }>(); });
- Test function return types:
test('createUser returns User', () => { expectTypeOf(createUser).returns.toMatchTypeOf<User>(); expectTypeOf(createUser).parameters.toEqualTypeOf<[name: string, email: string]>(); });
- Test generic inference:
test('identity preserves literal types', () => { const result = identity('hello'); expectTypeOf(result).toEqualTypeOf<'hello'>(); });
- Verify types are NOT assignable:
test('UserId and PostId are not interchangeable', () => { expectTypeOf<UserId>().not.toEqualTypeOf<PostId>(); expectTypeOf<UserId>().not.toMatchTypeOf<PostId>(); });
- Use
library for standalone type tests:expect-type
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 }>();
- Use
for declaration file testing:tsd
// 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.
- 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>(); });
- 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>(); });
- 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.
vs toEqualTypeOf
:toMatchTypeOf
— exact match.toEqualTypeOf
does NOT equal{ a: string }{ a: string; b?: number }
— structural compatibility.toMatchTypeOf
MATCHES{ a: string; b: number }{ a: string }
Tool comparison:
- Vitest
— integrated into vitest, runs alongside runtime tests, best for projects already using vitestexpectTypeOf
— standalone library, works with any test runner, same API as vitest's built-inexpect-type
— designed for library authors testing .d.ts files, uses a different assertion APItsd
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
assertions run at compile time, not runtime — test files must be type-checked by tscexpectTypeOf- 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
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- 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.