Mocha and Chai, Jasmine, QUnit

Jest 62,362 and Vitest 102,989 borrowed their shape from three older, still-maintained frameworks. You will meet them in existing codebases, Angular 42,088 projects and libraries that want a small runner.

Mocha and Chai

Mocha 120,373 (mochajs.org (https://mochajs.org/ 120,373 )) is only a runner: it provides describe, it, the hooks before, after, beforeEach and afterEach, and reporters. You bring the assertion library, usually Chai 206,458 (chaijs.com (https://www.chaijs.com/ 206,458 )),, plus Sinon 69,839 for mocks. First published in 2011, Mocha runs tests serially by default (--parallel uses workers) from the test/ folder. Install with npm 2,036 install -D mocha chai. When upgrading, note that Chai 5 (December 2023) and 6 ship as ES modules only, and Mocha 12 (August 2026) needs Node.js 20.19 2,131 or 22.12+.

test/cart.spec.js: Mocha hooks, a per-test timeout and Chai's chained expectJavaScript
import { expect } from "chai";
import { total } from "../src/cart.js";
describe("total()", function () {
  let items;
  beforeEach(function () {
    items = [{ price: 19.99, qty: 2 }, { price: 5, qty: 1 }];
  });
  it("throws a RangeError for a bad discount", function () {
    expect(() => total(items, -1)).to.throw(RangeError, /between 0 and 1/);
  });
  it("finishes a slow async check within 500 ms", async function () {
    this.timeout(500);
    await new Promise((resolve) => setTimeout(resolve, 300));
    expect(items).to.have.lengthOf(2).and.deep.include({ price: 5, qty: 1 });
  });
});
Output
> npx mocha
  total()
    ✔ throws a RangeError for a bad discount
    ✔ finishes a slow async check within 500 ms (309ms)
  2 passing (314ms)

The tests use function (), not arrows, because Mocha passes its context as this: this.timeout(500) fails inside an arrow function. Mocha prints durations for tests slower than 75 ms. Chai offers expect(x).to.equal(y), x.should.equal(y) and assert.equal(x, y) styles.

Jasmine

Jasmine 126 (jasmine.github.io (https://jasmine.github.io/ 126 )) needs no other packages: runner, expect matchers, spies and a fake clock are built in, and Jest originally ran its tests on a fork of Jasmine. Older Angular projects run Jasmine specs inside Karma 11,960 (Karma and Web Test Runner). npm install -D jasmine and npx jasmine init create spec/support/jasmine.mjs, which lists spec files and turns on random order. This spec tests a one-second autosave delay without waiting a real second:

spec/autosave.spec.js: a spy and the Jasmine clockJavaScript
function autosave(save, delay) {
  let timer;
  return (text) => {
    clearTimeout(timer);
    timer = setTimeout(() => save(text), delay);
  };
}
describe("autosave()", () => {
  beforeEach(() => jasmine.clock().install());
  afterEach(() => jasmine.clock().uninstall());
  it("saves once, after the user stops typing", () => {
    const save = jasmine.createSpy("save");
    const onInput = autosave(save, 1000);
    onInput("H");
    onInput("Hi");
    jasmine.clock().tick(999);
    expect(save).not.toHaveBeenCalled();
    jasmine.clock().tick(1);
    expect(save).toHaveBeenCalledOnceWith("Hi");
  });
});
Output
> npx jasmine
Randomized with seed 60547
Started
.
1 spec, 0 failures
Finished in 0.006 seconds
Randomized with seed 60547 (jasmine --random=true --seed=60547)

Random order exposes tests that depend on state left by earlier tests; rerun a failure with the printed seed. vi.useFakeTimers() and jest.useFakeTimers() do the job of the Jasmine clock.

QUnit

QUnit 67,568 (qunitjs.com (https://qunitjs.com/ 67,568 )) was written by John Resig inside jQuery 561 , became a separate project in 2008 and is now part of the OpenJS Foundation and the default test framework of Ember.js 94,851 . Assertions are methods on an assert object passed to each test, each with an optional message. QUnit runs in browsers, Node.js and Web Workers; its CLI prints TAP (Test Anything Protocol), a plain-text format CI systems parse.

qunit/cart.test.js: modules, tests and assertion messagesJavaScript
import { total } from "../src/cart.js";
QUnit.module("total()", () => {
  QUnit.test("adds and discounts", (assert) => {
    const items = [{ price: 19.99, qty: 2 }, { price: 5, qty: 1 }];
    assert.strictEqual(total(items), 44.98, "no discount");
    assert.strictEqual(total(items, 0.5), 22.49, "half price");
    assert.throws(() => total(items, 2), RangeError, "rejects 200%");
  });
});
Output
> npx qunit qunit/cart.test.js
TAP version 13
ok 1 total() > adds and discounts
1..1
# pass 1
# skip 0
# todo 0
# fail 0

All three are MIT-licensed; current versions are Mocha 12.0 with Chai 6.2, Jasmine 7.0 and QUnit 2.26. Migrating between them, or to Vitest, is mostly search and replace, but spy and mock APIs differ, so pick one framework per repository.