Object structure validation, property checking, and type manipulation utilities for testing object types and their properties.
Verify whether objects have specific properties and access their types.
/**
* Check if object has a given property and return its type for further testing
* @param key - Property key to check for
* @returns ExpectTypeOf for the property type if it exists, or true if using .not
*/
toHaveProperty<KeyType extends keyof Actual>(
key: KeyType
): KeyType extends keyof Actual ? PositiveExpectTypeOf<Actual[KeyType]> : true;Usage Examples:
import { expectTypeOf } from "expect-type";
const user = {
name: "John",
age: 30,
email: "john@example.com",
isActive: true
};
// Check property existence
expectTypeOf(user).toHaveProperty("name");
expectTypeOf(user).toHaveProperty("age");
expectTypeOf(user).not.toHaveProperty("address");
// Chain property type checking
expectTypeOf(user).toHaveProperty("name").toBeString();
expectTypeOf(user).toHaveProperty("age").toBeNumber();
expectTypeOf(user).toHaveProperty("isActive").toBeBoolean();
// Nested object properties
const nestedObj = {
data: {
items: [1, 2, 3],
metadata: { count: 3 }
}
};
expectTypeOf(nestedObj).toHaveProperty("data");
expectTypeOf(nestedObj.data).toHaveProperty("items");
expectTypeOf(nestedObj.data).toHaveProperty("metadata");
expectTypeOf(nestedObj.data.items).toBeArray();Select a subset of properties from an object type.
/**
* Select specific properties from an object type
* Equivalent to TypeScript's Pick utility type
* @param keyToPick - Property key to pick (optional for type inference)
* @returns ExpectTypeOf for the picked property subset
*/
pick<KeyToPick extends keyof Actual>(
keyToPick?: KeyToPick
): ExpectTypeOf<Pick<Actual, KeyToPick>, Options>;Usage Examples:
import { expectTypeOf } from "expect-type";
interface Person {
name: string;
age: number;
email: string;
address: {
street: string;
city: string;
};
}
// Pick single property
expectTypeOf<Person>()
.pick<"name">()
.toEqualTypeOf<{ name: string }>();
// Pick multiple properties
expectTypeOf<Person>()
.pick<"name" | "age">()
.toEqualTypeOf<{ name: string; age: number }>();
// Chain with other operations
expectTypeOf<Person>()
.pick<"address">()
.toHaveProperty("address")
.toEqualTypeOf<{ street: string; city: string }>();
// Pick with computed types
type UserKeys = "name" | "email";
expectTypeOf<Person>()
.pick<UserKeys>()
.toEqualTypeOf<{ name: string; email: string }>();Remove specific properties from an object type.
/**
* Remove specific properties from an object type
* Equivalent to TypeScript's Omit utility type
* @param keyToOmit - Property key to omit (optional for type inference)
* @returns ExpectTypeOf for the object type without the omitted properties
*/
omit<KeyToOmit extends keyof Actual | (PropertyKey & Record<never, never>)>(
keyToOmit?: KeyToOmit
): ExpectTypeOf<Omit<Actual, KeyToOmit>, Options>;Usage Examples:
import { expectTypeOf } from "expect-type";
interface Person {
name: string;
age: number;
email: string;
ssn: string; // sensitive data
}
// Omit single property
expectTypeOf<Person>()
.omit<"ssn">()
.toEqualTypeOf<{ name: string; age: number; email: string }>();
// Omit multiple properties
expectTypeOf<Person>()
.omit<"ssn" | "age">()
.toEqualTypeOf<{ name: string; email: string }>();
// Create public user type
type PublicUser = Omit<Person, "ssn">;
expectTypeOf<Person>()
.omit<"ssn">()
.toEqualTypeOf<PublicUser>();
// Omit with union types
expectTypeOf<Person>()
.omit<"ssn" | "email">()
.not.toHaveProperty("ssn");Advanced object structure validation with deep property checking.
Usage Examples:
import { expectTypeOf } from "expect-type";
// Deep object structure
const complexObject = {
user: {
profile: {
name: "Alice",
age: 25,
preferences: {
theme: "dark" as const,
notifications: true
}
},
permissions: ["read", "write"] as const
},
metadata: {
created: new Date(),
version: "1.0.0"
}
};
// Check deep property existence
expectTypeOf(complexObject)
.toHaveProperty("user")
.toHaveProperty("profile")
.toHaveProperty("preferences")
.toHaveProperty("theme")
.toEqualTypeOf<"dark">();
// Verify nested array properties
expectTypeOf(complexObject)
.toHaveProperty("user")
.toHaveProperty("permissions")
.items.toEqualTypeOf<"read" | "write">();
// Partial object matching
expectTypeOf(complexObject).toMatchObjectType<{
user: {
profile: {
name: string;
};
};
metadata: {
version: string;
};
}>();Handle optional properties and distinguish them from required ones.
import { expectTypeOf } from "expect-type";
interface ConfigOptions {
apiUrl: string; // required
timeout?: number; // optional
retries?: number; // optional
debug: boolean; // required
}
// Required properties must exist
expectTypeOf<ConfigOptions>().toHaveProperty("apiUrl");
expectTypeOf<ConfigOptions>().toHaveProperty("debug");
// Optional properties can be checked
expectTypeOf<ConfigOptions>().toHaveProperty("timeout");
expectTypeOf<ConfigOptions>().toHaveProperty("retries");
// Distinguish between optional and required
expectTypeOf<{ a?: number }>().not.toEqualTypeOf<{}>();
expectTypeOf<{ a?: number }>().not.toEqualTypeOf<{ a: number }>();
expectTypeOf<{ a?: number }>().not.toEqualTypeOf<{ a: number | undefined }>();
// Optional vs undefined union
expectTypeOf<{ a?: number | null }>()
.not.toEqualTypeOf<{ a: number | null }>();
// Nested optional properties
expectTypeOf<{ a: { b?: number } }>()
.not.toEqualTypeOf<{ a: {} }>();Distinguish between regular and readonly properties.
import { expectTypeOf } from "expect-type";
type MutableData = {
name: string;
count: number;
};
type ImmutableData = {
readonly name: string;
readonly count: number;
};
type MixedData = {
name: string;
readonly id: number;
};
// Readonly extends mutable (covariance)
expectTypeOf<ImmutableData>().toExtend<MutableData>();
// But they're not equal
expectTypeOf<ImmutableData>().not.toEqualTypeOf<MutableData>();
// Deep readonly differences
type DeepMutable = {
user: { name: string; age: number };
};
type DeepReadonly = {
user: { readonly name: string; readonly age: number };
};
expectTypeOf<DeepReadonly>().toExtend<DeepMutable>();
expectTypeOf<DeepReadonly>().not.toEqualTypeOf<DeepMutable>();
// Mixed readonly properties
expectTypeOf<MixedData>()
.pick<"name">()
.toEqualTypeOf<{ name: string }>();
expectTypeOf<MixedData>()
.pick<"id">()
.toEqualTypeOf<{ readonly id: number }>();Handle objects with index signatures and dynamic properties.
import { expectTypeOf } from "expect-type";
// String index signature
interface StringIndexed {
[key: string]: number;
}
// Number index signature
interface NumberIndexed {
[index: number]: string;
}
// Mixed known and index properties
interface MixedIndex {
name: string;
[key: string]: string | number;
}
// Check index signature behavior
expectTypeOf<StringIndexed>().toHaveProperty("anyKey");
expectTypeOf<NumberIndexed>().toHaveProperty(0);
expectTypeOf<NumberIndexed>().toHaveProperty(999);
// Mixed index validation
const mixedObj: MixedIndex = { name: "test", age: 30, active: 1 };
expectTypeOf(mixedObj).toHaveProperty("name").toBeString();
expectTypeOf(mixedObj).toHaveProperty("age"); // exists via index signatureValidate class instance types and their properties.
import { expectTypeOf } from "expect-type";
class User {
constructor(
public name: string,
private _id: number,
protected role: string = "user"
) {}
getName(): string {
return this.name;
}
private getId(): number {
return this._id;
}
}
// Public properties and methods
expectTypeOf<User>().toHaveProperty("name");
expectTypeOf<User>().toHaveProperty("getName");
// Private/protected members not accessible
expectTypeOf<User>().not.toHaveProperty("_id");
expectTypeOf<User>().not.toHaveProperty("getId");
expectTypeOf<User>().not.toHaveProperty("role");
// Method return types
expectTypeOf(new User("test", 1)).toHaveProperty("getName").returns.toBeString();
// Constructor instance type
expectTypeOf(User).instance.toEqualTypeOf<{
name: string;
getName(): string;
}>();