brittle

3.5.0 • Public • Published

brittle

tap à la mode

A TAP test runner built for modern times.

Usage

First install brittle from npm

npm i brittle

Then start writing tests

import test from 'brittle'

test('basic', function (t) {
  t.is(typeof Date.now(), 'number')
  t.not(typeof Date.now(), 'string')

  t.ok(Date.now() > 0)
  t.absent(null)

  t.comment('text')

  t.alike({ a: 1 }, { a: 1 })
  t.unlike({ a: 2 }, { a: 3 })

  t.pass()
  t.fail()
})

test('asynchronous', async function (t) {
  await new Promise(r => setTimeout(r, 250))
  t.pass()
})

test('plans', function (t) {
  t.plan(2)
  t.pass()
  setTimeout(() => t.pass(), 250)
})

test('classic subtest', function (t) {
  t.test('subtest', function (sub) {
    sub.plan(1)
    sub.pass()
  })
})

test('inverted subtest', function (t) {
  const sub = t.test('subtest')
  sub.plan(1)
  sub.pass()
})

test('executions', async function (t) {
  t.execution(() => 'should not throw')
  await t.execution(async () => 'should not reject')
})

test('exceptions', async function (t) {
  t.exception(() => { throw Error('expected to throw') })
  await t.exception(async () => { throw Error('expected to reject') })
})

const a = test('inverted test without plan needs end()')
a.pass()
a.end()

const b = test('inverted test with plan')
b.plan(1)
b.pass()

const c = test('inverted tests can be awaited')
c.plan(1)
setTimeout(() => c.pass(), 250)
await c

Every assertion can have a message, i.e. t.pass('msg'), t.ok(false, 'should be true'), etc.
There are also utilities like t.timeout(ms), t.teardown(fn), etc.
Check the API but also all the assertions here and utilities here.

API

import { test, solo, skip, todo, configure } from 'brittle'

test([name], [options], callback)

Create a classic test with an optional name.

Available options for any test creation:

  • timeout (30000) - milliseconds to wait before ending a stalling test.
  • solo (false) - Skip all other tests except the solo() ones.
  • skip (false) - skip this test, alternatively use the skip() function.
  • todo (false) - mark this test as todo and skip it, alternatively use the todo() function.

The callback function (can be async) receives an object called assert.
assert (or t) provides the assertions and utilities interface.

import test from 'brittle'

test('basic', function (t) {
  t.pass()
})

Test files can be executed directly with node, as they're normal Node.js programs.

The test method is conveniently both the default export and named exported method:

import { test } from 'brittle'

Classic tests will run sequentially, buffering pending tests until any prior test catches up.

Any test function returns a promise so you can optionally await for its result:

const isOk = await test('basic', function (t) {
  t.pass()
})

test([name], [options]) => assert

Create an inverted test with an optional name.

All options for inverted tests are listed here.

An object called assert (or t) is returned, the same as the classic test.

This time it's also a promise, it can be awaited and it resolves at test completion.

import test from 'brittle'

const t = test('basic')

t.plan(1)

setTimeout(() => {
  t.pass()
}, 1000)

await t // Won't proceed past here until plan is fulfilled

For inverted tests without a plan, the end method must be called:

const t = test('basic')

setTimeout(() => {
  t.pass()
  t.end()
}, 1000)

await t

The end() method can be called inline, for inverted tests without a plan:

const t = test('basic')
t.pass()
t.end()

Control flow of inverted is entirely dependent on where its assert is awaited.
The following executes one test after another:

const a = test('first test')
const b = test('second test')
a.plan(1)
b.plan(1)
a.pass()
await a
b.pass()
await b

Awaiting the promise gives you its result:

const t = test('first test')
t.plan(1)
t.pass()
const isOk = await t

t.test([name], [options], callback)

t.test([name], [options]) => assert

A subtest can be created by calling test on an assert (or t) object.
This will provide a new sub-assert object.

All options for subtests are listed here.

Using this in inverted style can be very useful for flow control within a test:

test('basic', async function (t) {
  const a = t.test('sub test')
  const b = t.test('other sub test')

  a.plan(1)
  b.plan(1)

  setTimeout(() => a.ok(true), Math.random() * 1000)
  setTimeout(() => b.ok(true), Math.random() * 1000)
  
  // Won't proceed past here until both a and b plans are fulfilled
  await a
  await b

  t.pass()
})

Subtest test options can be set by passing an object to the test function:

test('parent', { timeout: 1000 }, function (t) {
  t.test('basic using parent config', async function (t) {
    await new Promise(r => setTimeout(r, 500))
    t.pass()
  })

  t.test('another basic using parent config', function (t) {
    t.pass()
  })
})

You can also await for its result as well:

test('basic', async function (t) {
  t.plan(1)
  t.pass()
  const isOk = await t
  console.log(isOk)
})

solo([name], [options], callback)

solo([name], [options]) => assert

Filter out other tests by using the solo method:

import { test, solo } from 'brittle'

test('this test is skipped', function (t) {
  t.pass()
})

solo('some test', function (t) {
  t.pass()
})

If a solo function is used, test functions will not execute.\

If solo is used in a future tick (for example, in a setTimeout callback),
after test has already been used those tests won't be filtered.

A few ways to enable solo functions:

  • Use configure({ solo: true }) before any tests.
  • You can call solo() without callback underneath the imports.
  • Using the --solo flag with the brittle test runner.

It can also be used as an inverted test:

const t = test.solo('inverted some test')
t.pass()
t.end()

skip([name], [options], callback)

Skip a test:

import { test, skip } from 'brittle'

skip('this test is skipped', function (t) {
  t.pass()
})

test('middle test', function (t) {
  t.pass()
})

test.skip('another skipped test', function (t) {
  t.pass()
})

Only the middle test will be executed.

configure([options])

The configure function can be used to set options for all tests (including child tests).
It must be executed before any tests.

Options

  • timeout (30000) - milliseconds to wait before ending a stalling test
  • bail (false) - exit the process on first test failure
  • solo (false) - skip all other tests except the solo() ones
  • source (true) - shows error source information
import { configure } from 'brittle'

configure({ timeout: 15000 }) // All tests will have a 15 seconds timeout

Assertions

t.is(actual, expected, [message])

Compare actual to expected with ===

t.not(actual, expected, [message])

Compare actual to expected with !==

t.alike(actual, expected, [message])

Object comparison, comparing all primitives on the actual object to those on the expected object using ===.

t.unlike(actual, expected, [message])

Object comparison, comparing all primitives on the actual object to those on the expected object using !==.

t.ok(value, [message])

Checks that value is truthy: !!value === true

t.absent(value, [message])

Checks that value is falsy: !!value === false

t.pass([message])

Asserts success. Useful for explicitly confirming that a function was called, or that behavior is as expected.

t.fail([message])

Asserts failure. Useful for explicitly checking that a function should not be called.

t.exception(Promise|function|async function, [error], [message])

Verify that a function throws, or a promise rejects.

t.exception(() => { throw Error('an err') }, /an err/)
await t.exception(async () => { throw Error('an err') }, /an err/)
await t.exception(Promise.reject(Error('an err')), /an err/)

If the error is an instance of any of the following native error constructors, then this will still result in failure since native errors often tend to be unintentational.

  • SyntaxError
  • ReferenceError
  • TypeError
  • EvalError
  • RangeError

If a t.exception is async, then you're supposed to await it.

t.exception.all(Promise|function|async function, [error], [message])

Verify that a function throws, or a promise rejects, including native errors.

t.exception.all(() => { throw Error('an err') }, /an err/)
await t.exception.all(async () => { throw Error('an err') }, /an err/)
await t.exception.all(Promise.reject(new SyntaxError('native error')), /native error/)

The t.exception.all method is an escape-hatch so it can be used with the normally filtered native errors.

If a t.exception.all is async, then you're supposed to await it.

t.execution(Promise|function|async function, [message])

Assert that a function executes instead of throwing or that a promise resolves instead of rejecting. Resolves to the execution time, in milliseconds, of the function or promise.

t.execution(() => {})
await t.execution(async () => {})
await t.execution(Promise.resolve('cool'))

If a t.execution is async, then you're supposed to await it

t.is.coercively(actual, expected, [message])

Compare actual to expected with ==.

t.not.coercively(actual, expected, [message])

Compare actual to expected with !=.

t.alike.coercively(actual, expected, [message])

Object comparison, comparing all primitives on the actual object to those on the expected object using ==.

t.unlike.coercively(actual, expected, [message])

Object comparison, comparing all primitives on the actual object to those on the expected object using !=.

Utilities

t.plan(n)

Constrain a test to an explicit amount of assertions.

t.teardown(function|async function, [options])

Available options for teardowns:

  • order (0) - set the ascending position priority for a teardown to be executed.

The function passed to teardown is called right after a test ends:

test('basic', function (t) {
  const timeoutId = setTimeout(() => {}, 1000)

  t.teardown(async function () {
    clearTimeout(timeoutId)
    await doMoreCleanUp()
  })

  t.ok('cool')
})

If teardown is called multiple times in a test, every function passed will be called after the test ends:

test('basic', function (t) {
  t.teardown(doSomeCleanUp)

  const timeoutId = setTimeout(() => {}, 1000)
  t.teardown(() => clearTimeout(timeoutId))

  t.ok('again, cool')
})

Set order: -Infinity to always be in first place, and vice versa with order: Infinity.
If two teardowns have the same order they are ordered per time of invocation within that order group.

test('teardown order', function (t) {
  t.teardown(async function () {
    await new Promise(r => setTimeout(r, 200))
    console.log('teardown B')
  })

  t.teardown(async function () {
    await new Promise(r => setTimeout(r, 200))
    console.log('teardown A')
  }, { order: -1 })

  t.teardown(async function () {
    await new Promise(r => setTimeout(r, 200))
    console.log('teardown C')
  }, { order: 1 })

  t.pass()
})

The A teardown is executed first, then B, and finally C due to the order option.

t.timeout(ms)

Fail the test after a given timeout.

t.comment(message)

Inject a TAP comment into the output.

t.end()

Force end a test.
end is determined by assert resolution or when a containing async function completes.
In case of inverted tests, they're required to be explicitly called.

Runner

Default timeout

The default timeout is 30 seconds.

Example of package.json with test script

The following would run all .js files in the test folder:

{
  "name": "my-app",
  "version": "1.0.0",
  "scripts": {
    "test": "brittle test/*.js"
  },
  "devDependencies": {
    "brittle": "^3.0.0-alpha.3"
  }
}

CLI

npm install -g brittle@next
brittle [flags] [<files>]

Flags:
  -cov, --coverage              Turn on coverage
  --bail                        Bail out on first assert failure
  --solo                        Engage solo mode
  -r, --runner <out> <targets>  Generates an out file that contains all target tests

Note globbing is supported:

brittle --coverage path/to/test/*.js

Auto generate a single file containing "all tests":

brittle -r test/all.js test/*.js

node test/all.js

You can use an environment variable to also set flags:

BRITTLE="--coverage --bail" brittle test.js

Force disable coverage with an environment variable:

BRITTLE_COVERAGE=false brittle test.js

License

Apache-2.0

Readme

Keywords

none

Package Sidebar

Install

npm i brittle

Weekly Downloads

926

Version

3.5.0

License

Apache-2.0

Unpacked Size

55.4 kB

Total Files

9

Last publish

Collaborators

  • mafintosh
  • kasperisager
  • davidmarkclements
  • lukks