JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 1068
  • Score
    100M100P100Q98756F
  • License BlueOak-1.0.0

Uncommonly extensible assertions for the beautiful people

Package Exports

  • bupkis
  • bupkis/package.json

Readme

BUPKIS logo

⁓ BUPKIS ⁓

Uncommonly extensible assertions for the beautiful people
by @boneskull

Motivation

"Another assertion library? You cannot be serious. My test framework has its own assertions!"

‒sickos, probably

Look, I'm old wizened experienced knowledegable and I've written a lot of tests. I've used a lot of assertion libraries. There are ones I prefer and ones I don't.

But none of them do quite what BUPKIS does. I want an assertion library that prioritizes:

  • Type safety
  • Uncompromisable extensibility
  • A small API surface

I can think of several that tick two-thirds of those boxes! But I demand the total package (And You Should Too).

⚠️ Caution!

Assertion libraries tend come in two flavors: chainable or stiff & traditional. But because these styles are likely familiar to you, you may hate BUPKIS.

I want you to like it, yes. But if you don't, I'm content with just making my point and asking the following favor of the reader:

Do not confuse familiarity with usability.

The following is a brief overview of the design choices We made to serve these goals.

Natural Language Assertions

In BUPKIS, "natural language" is the means to the end of "a small API surface".

When you're using BUPKIS, you don't write this:

expect(actual).toEqual(expected);

Instead, you write this:

expect(actual, 'is', expected);
// or this
expect(actual, 'to be', expected);
// or this
expect(actual, 'to equal', expected);
// or even this
expect(actual, 'equals', expected);
// or yet another way
expect(actual, 'is equal to', expected);
// or believe it or not, even this
expect(actual, 'to strictly equal', expected);

If another assertion library wants you to write:

expect(actual).to.be.a('string');

Then BUPKIS wants you to write:

expect(actual, 'to be a string');
// it is tolerant of poor/ironic grammar, sometimes
expect(actual, 'to be an string');

Can't remember the phrase? Did you forget a word or make a typo? Maybe you also forgot BUPKIS is type-safe?! You'll get a nice squiggly for your trouble.

This isn't black magic. It ain't no cauldron. We're not just throwing rat tails and strings into a function and stirring that shit up.

"Preposterous! Codswallop!"

‒the reader and/or more sickos

You may wonder how this could this be anything but loosey-goosey senselessness. On the contrary—we have conventions!

Conventions of expect()

To formalize the conventions at a high level:

  • The first parameter to a BUPKIS assertion is always the subject (def.).

  • The "string" part of a BUPKIS assertion is known as a phrase. Every expectation will contain at minimum one (1) phrase. As you can see from the above "to be a string" example, phrases often have aliases.

  • Assertions may have multiple phrases or parameters, but the simplest assertions always look like this:

    expect(subject, 'phrase');

    ...and more complex assertions look like this:

    expect(subject, 'phrase', [parameter?, phrase?, parameter?, ...]);
  • One more convention worth mentioning is negation.

    You can negate just about any phrase by prepending it with not and a space. For example:

    expect(actual, 'to be', expected);
    expect(actual, 'not to be', expected);
    
    expect(
      () => throw new TypeError('aww, shucks'),
      'not to throw a',
      TypeError,
      'satisfying',
      /gol durn/,
    );

    Handy!

Custom Assertions Built With Zod

Zod is a popular object validation library which does some heavy lifting for _BUPKIS_—most of the built-in assertions are implemented using Zod schemas. And so BUPKIS extends this capability to you.

An example will be illuminating. What follows is a stupid quick stupid example of a creating and "registering" a basic assertion which can be invoked using two different phrases:

import { z, use, createAssertion } from 'bupkis';

const stringAssertion = createAssertion(
  z.string(),
  [['to be based', 'to be bussin']],
  z.string(),
);

const { expect } = use([stringAssertion]);

expect('chat', 'to be based');
expect('fam', 'to be bussin');

// did you know? includes all builtin assertions!
expect('skiball lavatory', 'to be a string');

📒 Registration?

"Registration" of an assertion (though there is no stateful "registry" anywhere) is as straightforward as passing an array of Assertion instances (created via createAssertion()/createAsyncAssertion()) to the use() function.

use(), as exported from bupkis, returns a new expect()/expectAsync() pair that includes your custom assertions alongside all the built-in ones. The new expect()/expectAsync() functions are fully type-safe and aware of your custom assertions. They also have a .use() method, which allows you to compose sets of assertions from disjoint sources.

Zod makes it extremely easy to create most custom assertions. But despite its power, it can't do everything we need an assertion to do; for those situations, there's also a function-based API for use with parametric and behavioral (e.g., involving function execution) assertions.

👉 For an assiduous guide on creating assertions, read Guide: How to Create a Custom Assertion.

Excruciating Type Safety

We have tried to make BUPKIS is as type-safe as possible. To be clear, that is pretty damn possible. This means:

  • Every built-in assertion is fully type-safe and is declared as an overload for expect() or expectAsync().
  • Every custom assertion is also fully type-safe and is declared as an overload for expect() or expectAsync() (as returned from use())
  • If an assertion demands the subject be of a certain type, the TS compiler will squawk if you try to use an incompatible subject type. For example, <Map> to have size <number> will only accept a Map as the subject, and this will be obvious in your editor.

Note: expect() is not and cannot be a type guard; see the "Caveats" Reference doc for more information.

Prerequisites

Node.js: ^20.19.0, ^22.12.0, >=23

BUPKIS has a peer dependency on Zod v4+, but will install it as an optional dependency if you are not already using it.

BUPKIS ships as a dual CJS/ESM package.

Disclaimer: BUPKIS has been designed to run on Node.js in a development environment. Anyone attempting to deploy BUPKIS to some server somewhere will get what is coming to them.

Installation

npm install bupkis -D

Usage

👉 See the Basic Usage Guide for a quick introduction.

📖 Visit https://bupkis.zip for comprehensive guides and reference.

Acknowledgements

  • Unexpected is the main inspiration for BUPKIS. However, creating types for this library is exceedingly difficult (and was in fact the first thing I tried). Despite that drawback, I find it more usable than any other assertion library I've tried.
  • Zod is a popular object validation library upon which BUPKIS builds many of its own assertions.
  • fast-check: Thanks to Nicholas Dubien for this library. There is no better library for an assertion library to use to test itself! Well, besides itself, I mean. How about in addition to itself? Yes. Thank you!
  • tshy from Isaac Schlueter. Thanks for making dual ESM/CJS packages easy and not too fancy.
  • TypeDoc it really documents the hell out of TypeScript projects.
  • @cjihrig and other Node.js contributors for the thoughtfulness put into node:test that make it my current test-runner-of-choice.

Why is it called BUPKIS?

TODO: think of good reason and fill in later

License

Copyright © 2025 Christopher Hiller. Licensed under BlueOak-1.0.0.