Manage Expectations in TypeScript with @push.rocks/smartexpect

Learn how @push.rocks/smartexpect provides a fluent, type-safe assertion library for TypeScript, making tests readable, maintainable, and powerful.

A fluent, type-safe assertion library that makes your tests read like documentation.

Introduction

Writing clear and maintainable tests is just as important as writing production code. Traditional assertion libraries can sometimes be verbose or lack the expressiveness needed for complex scenarios. Enter @push.rocks/smartexpect, a TypeScript-native assertion library that combines a fluent API with precise, descriptive error messages.

What is @push.rocks/smartexpect?

@push.rocks/smartexpect provides a rich set of built-in matchers and modifiers, allowing you to write synchronous and asynchronous assertions in a natural, chainable style. It integrates seamlessly with both Node.js and browser environments and is published on npm under the MIT license by Task Venture Capital GmbH.

Key highlights:

  • Fluent API: Chainable methods mirror natural language.
  • Sync & Async Support: .resolves and .rejects modifiers for promises.
  • Deep Navigation: Drill into nested objects and arrays with .property() and .arrayItem().
  • Rich Matchers: Over 50 built-in matchers for primitives, objects, arrays, dates, and more.
  • Custom Extensions: Define your own matchers with expect.extend().
  • Debugging: Inspect intermediate values with .log().

Installation

Install via npm or pnpm:

pnpm add @push.rocks/smartexpect --save-dev
# or using npm
npm install @push.rocks/smartexpect --save-dev

Getting Started

Import the expect function and start writing tests:

import { expect } from '@push.rocks/smartexpect';

// Basic type assertions
expect(42).toBeTypeofNumber();
expect('hello').toBeTypeofString();

// Equality checks
expect({ a: 1 }).toEqual({ a: 1 });
expect([1, 2, 3]).toContain(2);

Asynchronous Assertions

Handle promises with ease:

async function fetchUser(id: number): Promise<{ id: number; name: string }> {
  // ...fetch logic...
  return { id, name: 'Alice' };
}

// In your test:
await expect(fetchUser(1)).resolves.toHaveProperty('name', 'Alice');
await expect(fetchUser(-1)).rejects.toThrow();

Deeply nested data structures can be asserted without temporary variables:

const data = { users: [{ id: 1, profile: { active: true } }] };

expect(data)
  .property('users')
  .arrayItem(0)
  .property('profile')
  .property('active')
  .toBeTrue();

Custom Matchers and Messages

Extend expect with domain-specific assertions:

expect.extend({
  toBeEven(received: number) {
    const pass = received % 2 === 0;
    return { pass, message: () => `Expected ${received} to be even` };
  }
});

expect(4).toBeEven();
expect(5).not.toBeEven();

Override default failure messages for clarity:

expect(user.age)
  .setFailMessage('User must be at least 18 years old')
  .toBeGreaterThanOrEqual(18);

Debugging with .log()

Visualize intermediate values in assertion chains:

expect(order)
  .property('items')
  .log()
  .arrayItem(0)
  .log()
  .property('quantity')
  .toBeGreaterThan(0);

Getting Involved

Happy testing!

Read more