should
should.js is a framework-independent assertion library for JavaScript tests. It turns checks into readable chains such as user.should.have.property('name'), and covers deep equality, types, numbers, strings, containment, thrown errors, and promises. Its signature feature is a non-enumerable should getter on Object.prototype, though the package also provides a function-only entry point that avoids changing globals. The API is broad and still heavily downloaded, but the published release and most recent source work are years old.
Keep should.js where an established suite already depends on its fluent chains. Do not choose an archived, 2018 package for a new test stack when Chai, expect, and built-in runner assertions have active ecosystems and fewer global-side-effect concerns.
Use it if
- You maintain an existing test suite already written with should.js chains and want to avoid a large assertion rewrite
- You prefer readable BDD-style assertions but need them to work with Mocha or another test runner rather than a bundled runner
- You need one assertion package that includes deep containment, promise assertions, numeric ranges, and custom assertion extensions
- You can use the should/as-function entry point consistently to avoid modifying Object.prototype
- You want an actively maintained assertion library: npm 13.2.3 was published in July 2018, the last commit was in March 2019, and GitHub marks the repository archived
- You prohibit prototype modification: the default require('should') path installs a getter on Object.prototype, so every test file must use should/as-function to avoid that side effect
- You use ESM-first tooling and modern TypeScript: the README and bundled declarations document CommonJS-era imports, while the development toolchain still references TypeScript 2.5 and Rollup 0.53
- You need trustworthy browser support: the README points to a checked-in browser build, Bower, and a known-bugs wiki, while an open issue says browser testing needs rework
- You expect future fixes for promise and edge-case behavior: open reports include a rejectedWith breaking-change concern and an arrow-function throw bug, with no repository activity to suggest fixes are coming
Setup reality
Installation is only npm install --save-dev should, with no native build, peer dependency, credential, or configuration file. The important choice comes before the first assertion. require('should') mutates Object.prototype by adding a non-enumerable should getter; this is intentional, but it can violate lint rules, surprise code that works with unusual prototypes, and does not work on values created with Object.create(null). Use require('should/as-function') when global prototype changes are unacceptable, then write should(value) everywhere. The package is CommonJS-oriented. Its README shows import * as should from 'should' for TypeScript, and it ships should.d.ts, but that declaration surface comes from the 2018 release rather than a current TypeScript toolchain. Promise assertions return promises, so a test must return or await the chain or the runner can finish before failure arrives. Browser use is a separate old path: the README tells you to use the repository's should.js bundle or build one and refers to Bower plus a known-bugs page. There is no maintained ESM/browser export story. Finally, property() and length() move the active assertion object to the selected value, while filler words such as be, have, and which do nothing. Long chains can therefore assert against a different object than a reader expects unless they are kept short.
Patterns
Use should.js without changing Object.prototypeavoid-prototype-extension
const should = require('should/as-function');
should(result).have.property('status', 'ok');
should(null).not.be.ok();The default require('should') installs a getter on Object.prototype. The as-function entry point does not attempt that modification and also works for null and Object.create(null) values.
Check strict equalityassert-strict-equality
const should = require('should/as-function');
should(answer).equal(42);
should(label).exactly('ready');equal, equals, and exactly are aliases for strict equality. Use eql for structural comparison instead.
Compare objects and arrays deeplyassert-deep-equality
should(actual).eql({
id: 7,
roles: ['editor', 'reviewer'],
});eql is the deep comparison assertion; equal compares object identity and will fail for separately created objects with the same fields.
Check property names and valuesassert-properties
should(user)
.have.property('profile')
.which.have.properties({ name: 'Ada', active: true });property changes the current assertion object to that property's value. Assertions later in the same chain run against profile, not the original user.
Check types and instancesassert-types
should(items).be.an.Array();
should(total).be.a.Number();
should(createdAt).be.instanceOf(Date);
should(maybeValue).not.be.undefined();Capitalized helpers such as Array and Number are should.js methods, while instanceOf is the constructor check.
Check deep partial containmentassert-containment
should(response).containDeep({
user: { roles: ['admin'] },
});
should(['a', { id: 2 }]).containEql({ id: 2 });containDeep allows nested partial matches and unordered array containment. Use containDeepOrdered when array order is part of the contract.
Check string prefixes and patternsassert-string-pattern
should(filename).startWith('report-').and.endWith('.csv');
should(email).match(/^[^@]+@example\.com$/);match also accepts objects and functions, which can make its behavior less obvious. A regular expression is the clearest form for strings.
Check ranges and approximate numbersassert-number-range
should(statusCode).be.within(200, 299);
should(ratio).be.approximately(0.3, 0.001);
should(count).be.aboveOrEqual(1);approximately takes an absolute delta, not a percentage tolerance. within includes both boundary values.
Check a synchronous thrown errorassert-thrown-error
should(() => parseConfig('bad')).throw(Error, {
message: /invalid config/i,
});throw only observes synchronous exceptions. Use rejectedWith for a function that returns a rejected promise.
Check a rejected promiseassert-promise-rejection
await should(loadMissingUser()).be.rejectedWith(Error, {
message: /not found/i,
});Return or await the assertion. If the promise chain is left floating, the test runner can complete before should.js reports the failure.
Assert the eventual resolved valueassert-promise-result
await should(fetchCount()).eventually.be.a.Number().and.above(0);
await should(Promise.resolve({ ok: true })).be.fulfilledWith({ ok: true });eventually unwraps the fulfilled value for the remaining chain. fulfilledWith uses deep equality for the expected value.
Register a project-specific assertionadd-custom-assertion
const should = require('should/as-function');
should.Assertion.add('validUser', function () {
this.params = { operator: 'to be a valid user' };
should(this.obj).have.properties('id', 'email');
should(this.obj.id).be.a.Number();
});
should(user).be.a.validUser();Set this.params before checking the value so should.js can build a useful outer failure message. Custom assertions are global within the loaded should.js instance.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| chai | npm | You want a widely used BDD assertion API with ongoing releases and plugin support |
| expect | npm | You prefer Jest-style expect(value) assertions without installing a full test runner |
| power-assert | npm | You want standard assert expressions with richer failure output and accept a transform step |