should.js BDD Assertions
should.js is an expressive, BDD-style assertion library for JavaScript, designed to be framework-agnostic and provide helpful error messages. It is currently at version 13.2.3 and maintains an active release schedule with frequent patch and minor updates. A key differentiator is its default behavior of extending `Object.prototype` with a non-enumerable `should` getter, enabling `(value).should.be.something` syntax. For environments where `Object.prototype` extension is undesirable, it offers an alternative function-style API, `should(value).be.something`. The library ships with TypeScript type definitions, though specific import patterns have seen minor adjustments across versions. It aims for readability and clear test code.
Common errors
-
TypeError: Cannot read properties of null (reading 'should')
cause Attempting to use the `Object.prototype` extended `should` getter on `null` or `undefined` values, which do not inherit from `Object.prototype`.fixUse the function-style assertion API: `should(null).not.be.ok();` or `should(undefined).not.exist();`. Alternatively, ensure the value is not `null` or `undefined` before calling `.should`. -
AssertionError: expected Map {} to be deeply equal to Map {}cause This error, particularly when comparing two `Map` or `Set` objects where keys are different object instances but have equivalent content, is likely due to the breaking change in `should.js` v12.0.0 or later. This version changed Map/Set equality to use strict (`===`) key comparison.fixIf object key identity is not strictly necessary, restructure your tests to compare the `Map` or `Set` contents differently, perhaps by iterating and comparing values. If strict identity is intended, ensure the same key object instance is used. -
TypeError: (0).should.be.Number(1) is not a function
cause Passing an argument to a zero-argument assertion (e.g., `.be.Number()`, `.ok()`) which expects no arguments. This behavior started throwing a `TypeError` in v13.1.0.fixRemove any arguments passed to zero-argument assertions. For example, change `value.should.be.Number(someArg)` to `value.should.be.Number()`.
Warnings
- breaking Map and Set equality checks changed behavior significantly in v12.0.0. Previously, `Map` and `Set` equality checked value equality. From v12.0.0 onwards, checks align with standard key checks, meaning objects used as keys must be strictly equal (`===`) for maps to be considered equal.
- breaking The `.enumerable` and `.enumerables` assertions were removed in v13.0.0. These had been deprecated since v11.2.0 and are no longer available.
- breaking As of v13.1.0, all zero-argument assertions (e.g., `.ok()`, `.true()`, `.be.a.Number()`) will now throw a `TypeError` if any arguments are passed to them. This enforces correct usage of these specific assertions.
- gotcha By default, `should.js` extends `Object.prototype` with a non-enumerable `should` getter. While convenient, this can be problematic in some environments, for example, when working with `Object.create(null)` objects which do not inherit from `Object.prototype`, potentially causing unexpected behavior or conflicts.
- gotcha TypeScript users should generally import `should` using `import * as should from 'should';`. While `import should from 'should';` was temporarily fixed in v13.1.0 for default exports, this was reverted in v13.1.2, making the `* as` import the more robust and recommended approach for consistent behavior.
Install
-
npm install should -
yarn add should -
pnpm add should
Imports
- should (global)
import should from 'should';
import 'should'; // or for CommonJS: const should = require('should'); - should (as function)
import { should } from 'should/as-function';import should from 'should/as-function'; // or for CommonJS: const should = require('should/as-function'); - should (TypeScript)
import should from 'should';
import * as should from 'should';
Quickstart
import 'should'; // Or `const should = require('should');` for CommonJS to get global assertions
// Using the Object.prototype extension (default behavior)
const user = {
name: 'Alice',
age: 30,
isActive: true,
tags: ['developer', 'tester']
};
user.should.have.property('name', 'Alice');
user.age.should.be.a.Number().and.be.exactly(30);
user.tags.should.have.lengthOf(2).and.containEql('developer');
user.isActive.should.be.true();
// Using the function-style API (without Object.prototype modification)
import shouldAsFunction from 'should/as-function'; // Or `const shouldAsFunction = require('should/as-function');`
shouldAsFunction(null).not.be.ok();
shouldAsFunction(undefined).not.exist();
shouldAsFunction(user.name).be.exactly('Alice');
// Example for async assertions (assuming a promise-returning function)
async function fetchData(id: number): Promise<any> {
if (id === 1) return { id: 1, value: 'data' };
throw new Error('Not found');
}
(async () => {
try {
const result = await fetchData(1);
shouldAsFunction(result).be.an.Object().and.have.property('value', 'data');
} catch (err: any) {
shouldAsFunction(err).not.exist(); // This path should not be taken
}
try {
await fetchData(2);
} catch (err: any) {
shouldAsFunction(err).be.an.Error().and.have.property('message', 'Not found');
}
})();