Node TAP 19.0.2

tap Lifecycle Plugins

This describes plugins that add various test lifecycle hooks.

@tapjs/before#

@tapjs/before

A default tap plugin providing t.before().

USAGE#

This plugin is installed with tap by default. If you had previously removed it, you can tap plugin add @tapjs/before to bring it back.

import t from 'tap'
t.before(() => {
  // this will run before the tests in this file start
})

If the method returns a promise, it will be awaited before moving on to the next test.

A t.before() method will run prior to any subsequent child tests. If it's called before any child tests have started, then it will be run right away.

So, this test:

import t from 'tap'

t.before(() => {
  console.error('before initial')
})

t.test('first test', t => {
  t.before(async () => {
    // this will wait before moving on
    await new Promise(res => setTimeout(res, 100))
    console.error('before in first test')
  })
  console.error('in first test')
  t.test('child test', t => {
    console.error('child of first test')
    t.end()
  })
  t.end()
})

t.before(() => {
  console.error('before between')
})

t.test('second test', t => {
  console.error('in second test')
  t.end()
})

will print:

before initial
in first test
before in first test
child of first test
before between
in second test

Essentially, t.before() is a bit like a child test method that doesn't get a Test object as an argument.

@tapjs/before-each#

@tapjs/before-each

A default tap plugin providing t.beforeEach().

USAGE#

This plugin is installed with tap by default. If you had previously removed it, you can tap plugin add @tapjs/before-each to bring it back.

import t from 'tap'
t.beforeEach(t => {
  // this will run before each child test, all of their child
  // tests, and so on
  // the parameter is the child test that is about to start.
})

If the method returns a promise, it will be awaited before moving on to the next test.

The beforeEach method is called for all child tests, not just direct children. "Closer" ancestor beforeEach methods are called after further ancestors.

For example, this test:

import t from 'tap'
t.beforeEach(t => {
  console.error('root before each', t.name)
})

t.test('parent test', t => {
  t.beforeEach(t => {
    console.error('parent before each', t.name)
  })
  t.test('child test', t => t.end())
  t.end()
})

will print:

root before each parent test
root before each child test
parent before each child test

@tapjs/after#

@tapjs/after

A default tap plugin providing t.after() and t.teardown().

USAGE#

This plugin is installed with tap by default. If you had previously removed it, you can tap plugin add @tapjs/after to bring it back.

import t from 'tap'
t.after(() => {
  // this will run after all the tests in this file are done
})

The method can be called as either t.teardown() or t.after(). In an earlier version of tap, these had slightly different behaviors, but they are now the same.

If the method returns a promise, it will be awaited before moving on to the next test.

So, this test:

import t from 'tap'

t.test('first test', t => {
  t.teardown(async () => {
    // this will wait before moving on
    await new Promise(res => setTimeout(res, 100))
    console.error('end of first test teardown')
  })
  console.error('in first test')
  t.end()
})
t.test('second test', t => {
  console.error('in second test')
  t.end()
})

will print:

in first test
end of first test teardown
in second test

Order#

If multiple teardown methods are assigned to a single test, they will be run in reverse order of how they are assigned. This is a change from earlier versions of tap, and provides symmetry with t.before().

In practice, it can make things more straightforward, by keeping cleanup methods close to their associated setup logic. For example:

const connection = await connectToDB()
t.ok(connection, 'connected to database')
t.teardown(() => disconnectFromDB(connection))

const user1 = await createUser(connection)
t.ok(user1, 'created user 1')
t.teardown(() => deleteUser(connection, user1))

const user2 = await createUser(connection)
t.ok(user2, 'created user 2')
t.teardown(() => deleteUser(connection, user2))

If we delete the connection created in the first step before deleting the user records, then we can't use that connection to delete the user records.

This can also be accomplished with subtests, and a single teardown in each section:

t.test('user db tests', async t => {
  const connection = await connectToDB()
  t.ok(connection, 'connected to database')
  t.teardown(() => disconnectFromDB(connection))

  t.test('user 1', async t => {
    const user1 = await createUser(connection)
    t.ok(user1, 'created user 1')
    t.teardown(() => deleteUser(connection, user1))
  })

  t.test('user 2', async t => {
    const user2 = await createUser(connection)
    t.ok(user2, 'created user 2')
    t.teardown(() => deleteUser(connection, user2))
  })
})

@tapjs/after-each#

@tapjs/after-each

A default tap plugin providing t.afterEach().

USAGE#

This plugin is installed with tap by default. If you had previously removed it, you can tap plugin add @tapjs/after-each to bring it back.

import t from 'tap'
t.afterEach(t => {
  // this will run after each child test, all of their child
  // tests, and so on
  // the parameter is the child test that just ended.
})

If the method returns a promise, it will be awaited before moving on to the next test.

The afterEach method is called for all child tests, not just direct children. "Closer" ancestor afterEach methods are called before further ancestors.

For example, this test:

import t from 'tap'
t.afterEach(t => {
  console.error('root after each', t.name)
})

t.test('parent test', t => {
  t.afterEach(t => {
    console.error('parent after each', t.name)
  })
  t.test('child test', t => t.end())
  t.end()
})

will print:

parent after each child test
root after each child test
root after each parent test