Open-source project
chaijs/chai-as-promised avatar
chaijs/chai-as-promised

chai-as-promised: Promise Assertions for Chai Test Suites

Extends Chai with assertions about promises.

1,419 stars107 forksJavaScriptMIT

At a glance

What is it?
chai-as-promised adds promise-aware assertions to Chai, including fulfilled, rejected, and eventually interfaces. It is useful for JavaScript test suites that return promises, but its assertion-return semantics require care with older test runners and nonstandard promises.
Who is it for?
Adopt chai-as-promised when your suite already uses Chai, returns promises from tests, and needs concise assertions over fulfilled values or rejection reasons. Do not adopt it if your runner cannot consume returned promises without notify(done), or if the code under test exposes jQuery promises older than 3.0 or Angular promises without their documented extra setup.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The Assertion Gap chai-as-promised Addresses

chai-as-promised is for JavaScript developers testing asynchronous functions whose results arrive through Promises/A+-compatible objects. Its purpose is narrow: it lets a Chai assertion run against the eventual outcome of a promise instead of requiring each test to wire fulfillment and rejection handlers manually.

The README contrasts manual promise handling with a single fluent assertion. In the manual form, a fulfillment callback performs the Chai check and calls `done()`, while a rejection callback forwards the error. With chai-as-promised, the test can return the assertion itself:

javascript
return doSomethingAsync().should.eventually.equal("foo");

This removes repeated callback plumbing and preserves Chai's assertion vocabulary. It is intended for test code, not production promise handling, and it assumes Chai is already part of the test stack. The plugin supports Chai's `should`, `expect`, and `assert` styles rather than replacing Chai.

That makes it a fit for teams with an established Chai suite that wants consistent assertions for both synchronous values and asynchronous operations. It offers less value to projects using a different assertion library, or to tests where promise settlement is not the behavior under examination.

Eventual, Fulfilled, and Rejected Assertion Paths

The central mechanism is the `eventually` property. It converts an existing Chai assertion into one that evaluates the input promise's resolved value. The documented pattern accepts either a value that will be wrapped in a promise or a promise passed directly to `expect`:

javascript
(2 + 2).should.equal(4);

// becomes
return Promise.resolve(2 + 2).should.eventually.equal(4);


expect({ foo: "bar" }).to.have.property("foo");

// becomes
return expect(Promise.resolve({ foo: "bar" })).to.eventually.have.property("foo");

Promise-specific assertions check state or reason directly. The README lists `fulfilled`, `rejected`, and `rejectedWith(Error)` for the `should` interface, with corresponding `expect` forms. It also provides `become` as shorthand for a deep equality check on the fulfilled value.

The `assert` interface exposes `assert.eventually` and dedicated functions such as `isFulfilled`, `becomes`, `doesNotBecome`, and `isRejected`. A rejection check can match an error constructor, a regular expression for the message, or a substring:

javascript
return assert.isRejected(promise, Error, "optional message");
return assert.isRejected(promise, /error message regex matcher/, "optional message");
return assert.isRejected(promise, "substring to search error message for", "optional message");

By default, the promise returned by an assertion remains a Chai assertion object. Its `then` method comes from the input promise, allowing the test runner to await the assertion. The documentation says this behavior can be changed through `setTransferPromiseness`.

Returning the Assertion or Notifying a Test Runner

The most consequential constraint appears before the assertion vocabulary: promise assertions must be returned or connected to a runner callback. The README states that either `return` or `notify(done)` must be used.

Returning the assertion is the direct route because it lets a promise-aware runner observe settlement:

javascript
return promise.should.be.fulfilled;

Some runners do not treat a returned promise as the signal that an asynchronous test has finished. For those runners, the documented workaround transfers completion to the supplied `done()` function:

javascript
doSomethingAsync().should.eventually.equal("foo").notify(done);

This is not merely style. Omitting both paths can leave the test framework without a clear completion signal, even though the assertion itself is thenable. It also differs from the usual synchronous Chai pattern, where an assertion can stand alone. The README explicitly notes this departure from existing assertion style.

The plugin does not intrinsically assert a promise's progress callback. Its suggested boundary is to capture progress with a spy and make ordinary Chai assertions after settlement:

javascript
var progressSpy = sinon.spy();

return promise.then(null, null, progressSpy).then(function () {
    progressSpy.should.have.been.calledWith("33%");
    progressSpy.should.have.been.calledWith("67%");
    progressSpy.should.have.been.calledThrice;
});

That example introduces Sinon.JS and Sinon-Chai. It demonstrates that progress observation belongs outside the core promise assertion model.

A First Chai-as-Promised Test

The README does not provide an installation command. It directs users to the Chai as Promised package, while `package.json` identifies the package as `chai-as-promised`, declares `type` as `module`, and exposes the library through the package's root export. The repository also declares Chai as a peer dependency with the range `>= 2.1.2 < 7`.

Once `chai` and `chai-as-promised` are available in the test environment, a first assertion can follow the documented `should` pattern. This example asserts the fulfilled value of an asynchronous operation:

javascript
return doSomethingAsync().should.eventually.equal("foo");

For state checks, the assertion can stop after fulfillment:

javascript
return promise.should.be.fulfilled;

For rejection behavior, pass the promise to an `assert` call and choose the expected matcher:

javascript
return assert.isRejected(promise, Error, "optional message");

The expected observable result is determined by the promise under test. A fulfilled assertion succeeds only if the promise fulfills; a rejected assertion succeeds only if it rejects with an acceptable reason. The test runner must await the returned assertion for the outcome to affect the test result.

The repository's `package.json` defines `npm test` as `c8 mocha`, but that script runs the project's own test suite. It is not presented as an installation or user setup command. There is no documented browser bundle command, configuration file, or environment variable in the README, so the first-use tutorial should stay with the module import and assertion examples rather than invent setup steps.

Compatibility Boundaries for Promises and Test Runners

chai-as-promised is documented as compatible with promises that follow the Promises/A+ specification. That requirement matters because the implementation relies extensively on the standard transformation behavior of `then`, rather than merely attaching fulfillment and rejection callbacks.

Pre-3.0 jQuery promises are explicitly outside that boundary. The README says those promises did not follow the relevant specification and will not work with chai-as-promised. Angular promises receive a narrower warning: their digest cycle requires extra setup code. Consequently, installing the plugin does not make every object with promise-like methods safe to assert.

Test-runner support is another boundary. Mocha, Buster, and blue-tape are listed as runners that can use returned promises to signal asynchronous completion. Jasmine, QUnit, and tap/tape are described as runners that may not support that mechanism. For those runners, the README offers `notify(done)` rather than changing the plugin's underlying assertion model.

This compatibility split is more useful than a blanket statement that the package supports asynchronous tests. Teams should classify both the promise implementation and the runner's completion protocol before adopting it. A native Promises/A+-compatible promise and a returned Mocha test are a documented combination; an old jQuery Deferred or an unconfigured Angular promise is not.

Customization Hooks and Their Operational Cost

Two advanced hooks change behavior that is otherwise automatic. `setTransferPromiseness` controls which methods are exposed on the assertion object returned by chai-as-promised. The default provides `then`, derived from the input promise. The README shows how to add `finally` and `done` from another promise library:

js
import {setTransferPromiseness} from 'chai-as-promised';

setTransferPromiseness(function (assertion, promise) {
    assertion.then = promise.then.bind(promise);
    assertion.finally = promise.finally.bind(promise);
    assertion.done = promise.done.bind(promise);
});

The second hook, `setTransformAsserterArgs`, transforms assertion arguments before an asserter evaluates them. Its example increments every argument. A later example returns `Promise.all(args)`, allowing a range assertion such as `within` to receive resolved numbers instead of unresolved promises.

These hooks are useful when the local test stack needs another promise interface or when assertion arguments require asynchronous normalization. They also widen the mental and maintenance surface. A transformation that works for one assertion can alter equality, range, or matcher behavior elsewhere. The README documents the mechanism but does not provide compatibility tests for every custom callback, so teams should keep such transformations narrow and review the assertions affected by them.

Maintenance Signals, Package Metadata, and the MIT Boundary

The repository is not archived, and its last push was on 2026-10-12. The listed recent releases are v8.0.2 on 2025-08-22, v8.0.1 on 2024-11-23, and v8.0.0 on 2024-06-09. Those dates indicate recent release activity and a non-archived repository, but they do not by themselves describe the compatibility of any particular lockfile or test-runner version.

There is a concrete metadata point to verify before an upgrade. The repository file `package.json` contains version `7.1.2`, while the release list names v8.0.2 as the latest listed release. The package metadata declares Chai compatibility as `>= 2.1.2 < 7`, despite the development dependency entry using `^6.0.1`. An engineer should therefore confirm which package version is actually installed and whether it accepts the project's Chai version, rather than relying only on the release label.

The project uses the MIT license. That identifies the license declared by the repository and is relevant when reviewing redistribution and integration terms. This article does not interpret the license or provide legal advice. Upgrade cost is primarily code review: check the peer dependency range, confirm ESM compatibility through the package's `type` and `exports` fields, and review any `setTransferPromiseness` or `setTransformAsserterArgs` hooks, since those extensions can change assertion behavior across a suite.

chai-as-promised Compared with Manual Promise Handling

The real alternative is manual Chai assertions inside `then` fulfillment and rejection handlers. The README shows this approach directly, with a fulfillment branch that compares `result`, calls `done()`, and a rejection branch that forwards `err` to `done(err)`. A spy-based pattern is another alternative when the test needs to inspect progress callbacks, because chai-as-promised explicitly leaves that use to another kind of library.

Manual handling gives the test author direct control over the callback chain. It can be easier to understand when a test already depends on `done`, when each branch performs several operations, or when the runner's promise-return behavior is unusual. Its cost is repetitive plumbing, especially across a suite that checks resolved values and rejection reasons in many places. It also duplicates the distinction between an expected rejection and a failed assertion.

chai-as-promised keeps the Chai chain as the main interface. `eventually` reuses existing matchers, while `fulfilled` and `rejected` make promise states explicit. The trade-off is that the returned assertion becomes part of asynchronous completion, and custom hooks can introduce project-specific semantics. For a small number of one-off tests, manual handlers may be clearer. For repeated promise assertions in a Chai suite, the plugin removes a recurring source of callback boilerplate, provided the test runner and promise implementation satisfy its documented contract.

Editorial conclusion

Adopt chai-as-promised when your suite already uses Chai, returns promises from tests, and needs concise assertions over fulfilled values or rejection reasons. Do not adopt it if your runner cannot consume returned promises without notify(done), or if the code under test exposes jQuery promises older than 3.0 or Angular promises without their documented extra setup. Before upgrading, verify the installed Chai version against the declared peer range, confirm that the runner awaits returned promise assertions, and check whether any code relies on transferred `then`, `finally`, or `done` methods.

Frequently asked questions

How do I install and start using chai-as-promised with Chai?

The README does not provide an installation command, but `package.json` identifies the package as `chai-as-promised`, declares it as an ES module, and requires a compatible Chai peer dependency. The documented first assertion is `return doSomethingAsync().should.eventually.equal("foo");`.

Does chai-as-promised work with every promise-like object?

No. The README states compatibility with promises following the Promises/A+ specification and specifically says that jQuery promises before 3.0 will not work. Angular promises require extra setup because of their digest cycle.

What should I do if my test runner does not await returned promises?

The README says to use `notify(done)` when returning the assertion is not possible or preferred. It gives this form: `doSomethingAsync().should.eventually.equal("foo").notify(done);`.

Can chai-as-promised assert promise progress callbacks?

The README says it has no intrinsic support for testing promise progress callbacks. It suggests using a library such as Sinon.JS, possibly with Sinon-Chai, and checking a spy after the promise settles.

Official sources

  1. chaijs/chai-as-promised on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/chaijs-chai-as-promised.svg)](https://hysenlabs.com/projects/chaijs-chai-as-promised)

Was this useful?