TypeScript Conditional Type Checks Library: Static Type Testing
TypeScript is a powerful language with a sophisticated type system, including conditional types, mapped types, type inference, and more. However, developers often face a challenge: how do you verify that types work exactly as intended? Standard unit tests check values at runtime but cannot guarantee type correctness at compile time. That's where the conditional-type-checks library comes in.
This library provides a set of utilities for static type testing in TypeScript. It lets you write tests that verify whether a specific type meets expectations: whether it's a subtype of another, whether two types are equal, whether one value can be assigned to another, and so on. All checks happen at compile time, with zero runtime impact.
Why is this useful? First, for refactoring complex types — you can be confident that changes won't break expected behavior. Second, for documenting intent — type tests serve as living specifications. Third, for libraries that expose complex types — users will trust their correctness. The library is especially valuable when working with conditional types (extends, infer), template literals, and recursive types.
Installation
Install the library via npm as a dev dependency:
npm install --save-dev conditional-type-checks
Or via yarn:
yarn add --dev conditional-type-checks
No additional TypeScript configuration is needed — the library is purely type-based and generates no JavaScript code.
Quick Start
Create a file called type-tests.ts and write a simple test:
import { IsExact } from 'conditional-type-checks';
type TestType = string;
type ExpectedType = string;
// Check that the types match exactly
type TestPass = IsExact<TestType, ExpectedType>; // true
// If types don't match, a compile error occurs
// type TestFail = IsExact<TestType, number>; // false
To make the test actually assert something, use the assert utility or a simple assignment with validation:
import { assert } from 'conditional-type-checks';
type MyType = { name: string; age: number };
// Verify that MyType is an object with name and age fields
type Check = assert<IsExact<MyType, { name: string; age: number }>>; // OK
If the check fails, TypeScript throws a compile error with the message Type 'false' does not satisfy the constraint 'true'.
Key Methods and Functions
The library provides several core utilities. Let's explore each one in detail.
1. IsExact<T, U>
Signature: type IsExact<T, U> = ...
What it does: Checks whether types T and U are exactly the same. Returns true if the types are identical, otherwise false. It accounts for all TypeScript nuances, including readonly, optional, and other modifiers.
import { IsExact } from 'conditional-type-checks';
type A = { readonly x: number };
type B = { x: number };
type Test1 = IsExact<A, B>; // false — readonly differs
type Test2 = IsExact<string, string>; // true
type Test3 = IsExact<string | number, number | string>; // true — order doesn't matter
type Test4 = IsExact<{ a: string }, { a: string; b?: number }>; // false
2. IsAny<T>
Signature: type IsAny<T> = ...
What it does: Checks whether type T is exactly any. Returns true if T is any, otherwise false. This is useful for catching accidental any types that slip through type checking.
import { IsAny } from 'conditional-type-checks';
type Test1 = IsAny<any>; // true
type Test2 = IsAny<string>; // false
type Test3 = IsAny<unknown>; // false
type Test4 = IsAny<never>; // false
3. IsNever<T>
Signature: type IsNever<T> = ...
What it does: Checks whether type T is never. Returns true if T is never, otherwise false. This is particularly useful when working with conditional types that may produce never as an edge case.
import { IsNever } from 'conditional-type-checks';
type Test1 = IsNever<never>; // true
type Test2 = IsNever<string>; // false
type Test3 = IsNever<any>; // false
4. IsUnknown<T>
Signature: type IsUnknown<T> = ...
What it does: Checks whether type T is unknown. Returns true if T is unknown, otherwise false. This helps ensure type safety when dealing with dynamic data.
import { IsUnknown } from 'conditional-type-checks';
type Test1 = IsUnknown<unknown>; // true
type Test2 = IsUnknown<any>; // false
type Test3 = IsUnknown<string>; // false
5. IsUnion<T>
Signature: type IsUnion<T> = ...
What it does: Checks whether type T is a union type. Returns true if T is a union of two or more types, otherwise false. This is useful for validating that a type is not accidentally a union when it should be a single type.
import { IsUnion } from 'conditional-type-checks';
type Test1 = IsUnion<string | number>; // true
type Test2 = IsUnion<string>; // false
type Test3 = IsUnion<never>; // false
6. assert<T extends true>
Signature: type assert<T extends true> = ...
What it does: A utility that enforces a type-level assertion. It takes a type parameter that must extend true. If the condition is false, TypeScript raises a compile-time error. This is the primary way to write meaningful type tests.
import { assert, IsExact } from 'conditional-type-checks';
type MyType = { id: number; name: string };
// This passes — types match
type TestPass = assert<IsExact<MyType, { id: number; name: string }>>;
// This fails — types don't match
// type TestFail = assert<IsExact<MyType, { id: number }>>;
Practical Examples
Let's look at some real-world scenarios where conditional-type-checks shines.
Testing Conditional Types
import { assert, IsExact } from 'conditional-type-checks';
// A conditional type that extracts the return type from a function
type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : never;
// Test it
type Test1 = assert<IsExact<ReturnTypeOf<() => string>, string>>; // passes
type Test2 = assert<IsExact<ReturnTypeOf<(x: number) => boolean>, boolean>>; // passes
type Test3 = assert<IsExact<ReturnTypeOf<string>, never>>; // passes — not a function
Testing Mapped Types
import { assert, IsExact } from 'conditional-type-checks';
// A mapped type that makes all properties optional
type Partialize<T> = { [K in keyof T]?: T[K] };
type Original = { name: string; age: number };
type Expected = { name?: string; age?: number };
type Test = assert<IsExact<Partialize<Original>, Expected>>; // passes
Testing Template Literal Types
import { assert, IsExact } from 'conditional-type-checks';
// A template literal type for event handlers
type EventHandler<Event extends string> = `on${Capitalize<Event>}`;
type Test = assert<IsExact<EventHandler<'click'>, 'onClick'>>; // passes
Best Practices
To get the most out of conditional-type-checks, follow these guidelines:
- Keep tests alongside your types — place type tests in a separate file (e.g.,
type-tests.ts) or co-locate them with your type definitions. - Test edge cases — always test
never,any,unknown, and union types to ensure your conditional types handle them correctly. - Use descriptive test names — even though these are types, comments help document what each test verifies.
- Integrate into CI — since type checks happen at compile time, they run automatically when you build. Ensure your CI pipeline runs
tsc --noEmitto catch failures. - Combine with runtime tests — use type tests for compile-time guarantees and runtime tests (e.g., Jest) for runtime behavior.
Conclusion
The conditional-type-checks library is an essential tool for any TypeScript developer working with complex type systems. It brings the rigor of unit testing to the type level, ensuring your types behave correctly before your code ever runs. By catching type errors at compile time, you reduce bugs, improve code quality, and make your type definitions self-documenting.
Whether you're building a library with intricate generic types, refactoring a large codebase, or just want to write safer TypeScript, conditional-type-checks gives you the confidence that your types are correct. Install it today and start writing type-level tests that compile-time guarantees.
Also in library
JSON Processing in C++: Libraries, Parsing and Data Serialization
OAuth Client for Java: Overview of Libraries and Code Examples
Working with Dates in Rust: Overview of Time and Calendar Libraries
ImageSharp for C# (.NET): Complete Guide with Examples
Paging 3 for Kotlin: Complete Guide with Examples