C / C++ / C# / Java / CoffeeScript / JavaScript / Node / Angular / Vue / Cycle.js / functional programming / testing
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
}
}
jest v29 7 years
mocha v10 11 years
npx available-versions mocha
tape v5 10 years
tap v16 11 years
ava v5 9 years
Node.js 13 years
May 2009
Write your own test runner!
JavaScript standard library is tiny
Node.js does not include test runner
Write your own test runner!
Mocha + Chai + Sinonjs
Ava
Ava (Node)
Cypress (browser)
Ported to v16.x
I am using v19.6.0
Test examples shown in this presentation: https://github.com/bahmutov/node-tests
2022-04-19
2022-04-19
2023-03-08
import test from 'node:test'
New built-in module "node:test"
$ node --test tests/*.mjs
New --test
Node CLI flag
import test from 'node:test'
import assert from 'node:assert/strict'
test('hello', () => {
const message = 'Hello'
assert.equal(message, 'Hello', 'checking the greeting')
})
Every test shown in this presentation: https://github.com/bahmutov/node-tests
import test from 'node:test'
import assert from 'node:assert/strict'
test('hello', () => {
const message = 'Hello'
assert.equal(message, 'Hello', 'checking the greeting')
})
$ nvm ls-remote
$ nvm install 19
Downloading and installing node v19.6.0...
Downloading https://nodejs.org/dist/v19.6.0/...
############################################## 100.0%
Computing checksum with shasum -a 256
Checksums matched!
Now using node v19.6.0 (npm v9.4.0)
🎁 Just use "nvm" https://github.com/nvm-sh/nvm
Test runner | NPM packages installed |
---|---|
Mocha | 78 |
Mocha + Chai + Sinon | 445 |
Ava | 198 |
Jest | 429 |
$ node tests/demo.mjs
Tip: you can run tests in any JS file that has import "node:test" command
$ node --watch tests/demo.mjs
$ node --watch --test tests/*.mjs
Tip: test runner is works with built-in Node --watch mode
Tip: finds the tests by default
$ node --test
# runs tests in "test" subfolder
Start or end test file names with "test" to automatically recursively find them
tests/
names/
another.test.mjs
my_test.mjs
test-1.mjs
utils.mjs
$ node --test tests/names
- tests/names/another.test.mjs
- tests/names/my_test.mjs
- tests/names/test-1.mjs
Test file convention
https://nodejs.org/api/test.html#test-runner-execution-model
describe('parallel tests', { concurrency: true }, () => {
it('subtest 1', async () => {
console.log('subtest 1 start')
await delay(5000)
console.log('subtest 1 end')
})
it('subtest 2', async () => {
console.log('subtest 2 start')
await delay(5000)
console.log('subtest 2 end')
})
})
describe('parallel tests', { concurrency: true }, () => {
it('subtest 1', async () => {
console.log('subtest 1 start')
await delay(5000)
console.log('subtest 1 end')
})
it('subtest 2', async () => {
console.log('subtest 2 start')
await delay(5000)
console.log('subtest 2 end')
})
})
import test from 'node:test'
import assert from 'node:assert/strict'
test('top level test', async (t) => {
await t.test('subtest 1', (t) => {
assert.strictEqual(1, 1)
})
await t.test('subtest 2', (t) => {
assert.strictEqual(2, 2)
})
})
Every test shown in this presentation: https://github.com/bahmutov/node-tests
import test from 'node:test'
import assert from 'node:assert/strict'
test('top level test', async (t) => {
await t.test('subtest 1', (t) => {
assert.strictEqual(1, 1)
})
await t.test('subtest 2', (t) => {
assert.strictEqual(2, 2)
})
})
Every test shown in this presentation: https://github.com/bahmutov/node-tests
import test from 'node:test'
import assert from 'node:assert/strict'
test('top level test', async (t) => {
await test('subtest 1', (t) => {
assert.strictEqual(1, 1)
})
await t.test('subtest 2', (t) => {
assert.strictEqual(2, 2)
})
})
Every test shown in this presentation: https://github.com/bahmutov/node-tests
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
describe('top level test', () => {
it('subtest 1', () => {
assert.strictEqual(1, 1)
})
it('subtest 2', () => {
assert.strictEqual(2, 2)
})
})
Every test shown in this presentation: https://github.com/bahmutov/node-tests
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
describe('top level test', () => {
it('subtest 1', () => {
assert.strictEqual(1, 1)
})
it('subtest 2', () => {
assert.strictEqual(2, 2)
})
})
1
2
$ node --test --test-reporter tap # default
$ node --test --test-reporter spec
$ node --test --test-reporter dot
$ node --test --test-reporter dot \
--test-reporter-destination stdout \
--test-reporter spec \
--test-reporter-destination out.txt
multiple reporters
Test Anything Protocol
Demo "node --test" vs "node --test | npx faucet"
# npm i -D faucet
$ node --test | npx faucet
"node --test" plus https://github.com/ljharb/faucet output
it('works', () => {
assert.equal(1, 1)
})
it('fails', () => {
assert.equal(2, 5)
})
it.todo('loads data')
// SKIP: <issue link>
it.skip('stopped working', () => {
assert.equal(2, 5)
})
before(() => {
console.log('before hook')
throw new Error('Setup fails')
})
it('works', () => {
assert.equal(1, 1)
})
it('works again', () => {
assert.equal(1, 1)
})
🐞
via built-in Node assert module
import assert from 'node:assert/strict'
assert.ok(truthy, message)
assert.equal(value, expected, ...)
assert.deepEqual(...)
assert.match(value, regexp)
assert.throws(fn)
assert.rejects(asyncFn)
plus ".notX" assertions
quite sparse compared to https://www.chaijs.com/api/bdd/
expect({ /* object */ }).to.have.property(x)
expect({ /* object */ }).to.have.keys([x, y, z])
expect({ /* large object */ })
.to.deep.include({ ... known properties })
// import assert from 'node:assert/strict'
import { assert } from 'chai'
via built-in Node assert module
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
describe('Assertions', () => {
it('passes with primitives', () => {
assert.equal('Hello', 'Helloz', 'greeting check')
})
})
fail the assertion on purpose
via built-in Node assert module
it('fails objects on purpose', () => {
const person = { name: { first: 'Joe' } }
assert.deepEqual(person,
{ name: { first: 'Anna' } }, 'people')
})
fail the assertion on purpose
with its "magic assert"
import test from 'ava'
test('fails objects on purpose', (t) => {
const person = { name: { first: 'Joe' } }
t.deepEqual(person,
{ name: { first: 'Anna' } }, 'people')
})
fail the assertion on purpose
with its "magic assert"
import test from 'ava'
test('passes with primitives', (t) => {
t.is('Hello', 'Helloz', 'greeting check')
})
fail the assertion on purpose
import { it } from 'mocha'
import { expect } from 'chai'
it('fails objects on purpose', (t) => {
const person = { name: { first: 'Joe' } }
expect(person).to.deep.equal(
{ name: { first: 'Anna' } })
})
fail the assertion on purpose
import { it } from 'node:test'
import { expect } from 'chai'
it('fails objects on purpose', () => {
const person = { name: { first: 'Joe' } }
expect(person).to.deep.equal({ name: { first: 'Anna' } })
})
it('works @sanity', () => {
assert.equal(1, 1)
})
it('works @sanity and @feature-a', () => {
assert.equal(2, 2)
})
it('low-priority-test', () => {
assert.equal(3, 3)
})
# run tests with "@sanity" in the title
--test --test-name-pattern @sanity
name: ci
on: push
jobs:
name: test
steps:
- uses: actions/checkout@v3
# https://github.com/actions/setup-node
- uses: actions/setup-node@v3
with:
node-version: 19.6.0
cache: 'npm'
- run: npm ci
- run: npm run spec
🏎️🏎️🏎️🏎️🏎️
1. Spy / stub methods when you have an object reference
import { it, mock } from 'node:test'
import assert from 'node:assert/strict'
it('returns name', () => {
const person = {
name() {
return 'Joe'
},
}
assert.equal(person.name(), 'Joe')
mock.method(person, 'name', () => 'Anna')
assert.equal(person.name(), 'Anna')
assert.equal(person.name.mock.calls.length, 1)
person.name.mock.restore()
assert.equal(person.name(), 'Joe')
})
1. Spy / stub methods when you have an object reference
$ npm i -D typescript ts-node
{
"scripts": {
"ts-test": "node --test --loader ts-node/esm test/**/*.ts"
}
}
package.json
import { it } from 'node:test'
import assert from 'node:assert/strict'
type Person = {
name: string
}
it('subtest 1', () => {
console.log('testing the person')
const p: Person = {
name: 'Joe',
}
assert.deepEqual(p, { name: 'Joe' })
})
test/ts-tests.ts
TypeScript .ts file
JavaScript .mjs file
it('works for 2 seconds', { timeout: 1000 }, async () => {
await delay(2000)
})
it('works for 2 seconds', { skip: 'Issue url here' },
async () => {
await delay(2000)
})
it('works for 2 seconds', { only: true }, async () => {
await delay(2000)
})
it('fails', () => {
throw new Error('Nope')
})
it('works for 2 seconds', { only: true }, async () => {
await delay(2000)
})
it('fails', () => {
throw new Error('Nope')
})
it('succeeds', (done) => {
setTimeout(done, 1000)
})
it('succeeds', (done) => {
setTimeout(() => {
done(new Error('A problem'))
}, 1000)
})
import test from 'node:test'
import assert from 'node:assert/strict'
async function fetchTestData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(['first', 'second', 'third'])
}, 1000)
})
}
test('generated', async (t) => {
const items = await fetchTestData()
for (const item of items) {
await t.test(`test ${item}`, (t) => {
assert.strictEqual(1, 1)
})
}
})
import test from 'node:test'
import assert from 'node:assert/strict'
async function fetchTestData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(['first', 'second', 'third'])
}, 1000)
})
}
test('generated', async (t) => {
const items = await fetchTestData()
for (const item of items) {
await t.test(`test ${item}`, (t) => {
assert.strictEqual(1, 1)
})
}
})
import { it } from 'node:test'
import assert from 'node:assert/strict'
import { calculate } from './calculator.mjs'
it('adds two numbers', () => {
assert.strictEqual(calculate('+', 2, 3), 5, '2+3')
})
export const add = (a, b) => {
console.log('adding %d to %d', a, b)
return a + b
}
export const sub = (a, b) => {
console.log('%d - %d', a, b)
return a - b
}
math.mjs
--experimental-test-coverage
Only tested half of math.mjs
t.plan(2)
jest.useFakeTimers()
deno test --fail-fast
test.failing('found a bug', t => {
// Test will count as passed
t.fail();
});
Feature | Node.js TR | Mocha | Ava | Jest |
---|---|---|---|---|
Included with Node | ✅ | 🚫 | 🚫 | 🚫 |
Watch mode | ✅ | ✅ | ✅ | ✅ |
Reporters | via TAP | strong | via TAP | lots |
Assertions | weak | via Chai ✅ | ✅ | ✅ |
Snapshots | 🚫 | 🚫 | ✅ | ✅ |
Hooks | ✅ | ✅ | ✅ | ✅ |
grep support | ✅ | ✅ | ✅ | ✅ |
spy and stub | ✅ | via Sinon ✅ | via Sinon ✅ | ✅✅ |
parallel execution | ✅ | ✅ | ✅ | ✅ |
code coverage | ✅ | via nyc | via c8 | ✅ |
TS support | via ts-node | via ts-node | via ts-node | via ts-jest |
node --test
on smaller new projects