Earl Assertion Library
website·Indexed 32 days ago
https://earl.fun/An ergonomic, modern, and type-safe assertion library written in TypeScript. Earl provides a comprehensive suite of validators, matchers, and deep equality algorithms for testing, including support for snapshot testing, Zod schema validation, and custom extensions.
What's inside Earl
- Earl is a modern, type-safe assertion library for the TypeScript ecosystem. It is designed to combine the simplicity and speed of runners like Mocha with a robust, type-safe API for assertions, aiming to replace less type-safe alternatives like Chai.
Create and assert mocks with mockObject
UsemockObjectto substitute real functions or objects with mocks. You only need to define the values you care about; other properties will error on access. Earl provides built-in validators, such as.toHaveBeenOnlyCalledWith(), to verify mock interactions.import { expect, mockObject } from 'earl' interface IApi { getUser(id: number): Promise<User> getOrders(userId: number): Promise<Order[]> } // Create mock const api = mockObject<IApi>({ getUser: async (id) => ({ id, name: 'John', email: '[email protected]' }), }) const user = await api.getUser(1) expect(user.name).toEqual('John') // Assert mock was called with specific arguments expect(api.getUser).toHaveBeenOnlyCalledWith(1)Understand the earl Equality Algorithm
The equality algorithm is an extensible and parameterized deep equality checker used by all deep equality functions in earl. It determines equality through a three-step process:
- Matcher Check: If the second value is a Matcher, equality is based on whether the first value matches it.
- Circular Reference Check: If a value is self-referencing (equal to an ancestor), it is considered equal to another value if that value is also equal to its ancestor at the same distance.
- Category Comparison: Values are assigned to a category. If categories differ, values are unequal. If they match, they are compared using the specific rules for that category.
Verify asynchronous rejections with toBeRejected
Use thetoBeRejectedvalidator to check if an asynchronous function or a promise is rejected. Because this involves asynchronous code, you must use theawaitkeyword when callingexpect.async function fetchResource() { throw new Error('Failed to fetch resource') } const rejectedPromise = Promise.reject(new Error('Something went wrong')) await expect(fetchResource()).toBeRejected() await expect(rejectedPromise).toBeRejected()Bootstrap a new TypeScript project for Earl
To set up a new project from scratch, initialize apackage.jsonwith test scripts and a.gitignoreto exclude build artifacts and dependencies.// package.json { "name": "my-project", "private": true, "scripts": { "build": "tsc", "test": "mocha" } }# .gitignore node_modules distInstall Earl assertion library
Earl is a modern, type-safe assertion library written in TypeScript. It is designed to be used with test runners such as mocha, uvu, or node:test.# Using npm npm install --save-dev earl # Using yarn yarn add --dev earl # Using pnpm pnpm add --save-dev earlCheck value equality using toEqual, toLooseEqual, and toExactlyEqual
Earl provides three primary validators for equality depending on the required strictness:
toEqual: The standard choice for objects and primitives. It uses a deep equality algorithm, checks prototypes, is type-safe, and supports matchers.toLooseEqual: A non-type-safe version oftoEqualthat performs deep equality checks but ignores prototypes and types. Supports matchers.toExactlyEqual: Checks for reference equality using strict equality. Recommended primarily for objects, as it behaves identically totoEqualfor primitives.
class Vector { constructor(public x: number, public y: number) {} } // Deeply check values, TypeScript types and prototypes expect(new Vector(1, 2)).toEqual(new Vector(1, 2)); // Only deeply check values (ignores prototype/type) expect(new Vector(1, 2)).toLooseEqual({ x: 1, y: 2 }); // Only check reference equality const v = new Vector(1, 2); expect(v).toExactlyEqual(v);Configure TypeScript for Earl
Install TypeScript and create atsconfig.jsonin the project root. A minimal configuration is required to ensure Earl is resolved correctly and output is directed to adistfolder.npm install --save-dev typescript// tsconfig.json { "compilerOptions": { "lib": ["ES2020"], "target": "ES2020", "strict": true, "moduleResolution": "node", "outDir": "dist" } }Verify asynchronous rejection reasons with toBeRejectedWith
Use thetoBeRejectedWithvalidator to verify the specific reason for a promise rejection. Similar totoThrow, it accepts a specific error message (string or regex) or a combination of an error class and a message. This must be used withawait.async function fetchResource() { throw new Error('Failed to fetch resource') } const rejectedPromise = Promise.reject(new Error('Custom error message')) // Verify using Error class and a regex pattern await expect(fetchResource()).toBeRejectedWith(Error, /resource/) // Verify using a specific error message string await expect(rejectedPromise).toBeRejectedWith('Custom error message')Create mock objects with mockObject
ThemockObjectfunction creates a mock object of a specific TypeScript type. You provide an object containing the properties you wish to mock; any property accessed that was not explicitly mocked will throw an error. Functions defined in the mock object are automatically replaced with mock functions, allowing you to usemockFncustomization methods on them.import { mockObject } from 'earl' class Person { constructor(public name: string, public age: number) {} isAdult() { return this.age >= 18 } } const mock = mockObject<Person>({ isAdult: () => true, }) expect(mock.isAdult()).toEqual(true) // Reconfigure the mock function behavior mock.isAdult.returns(false) // Accessing unmocked properties throws expect(() => mock.name).toThrow('no mock value provided')Implement snapshot testing with Earl
Snapshot testing in Earl compares the output of your code against a stored snapshot to ensure large outputs do not change unexpectedly. Because Earl is an assertion library and not a full framework, you must provide the current test context to the
toMatchSnapshotmethod so Earl can identify the running test and select the correct snapshot file.Snapshots are stored in
.snapshotfiles located in the same directory as the test file. To update existing snapshots, set theUPDATE_SNAPSHOTSenvironment variable totrue.Validate values using Zod schemas in Earl
Earl provides built-in support for Zod schema validation. You can use thetoMatchSchemavalidator to verify a single value against a schema, or theexpect.schema()matcher to validate elements within a collection (such as an array) usingtoEqual.import * as z from 'zod' import { expect } from 'earl' const schema = z.object({ foo: z.string(), bar: z.number(), }) // Validate a single object expect({ foo: 'foo', bar: 1 }).toMatchSchema(schema) // Validate multiple objects within an array expect([ { foo: 'one', bar: 1 }, { foo: 'two', bar: 2 }, ]).toEqual([expect.schema(schema), expect.schema(schema)])