1.0.1 • Published 5 years ago

aquatap v1.0.1

Weekly downloads
2
License
MIT
Repository
github
Last release
5 years ago

aquatap

fullstack TAP with a modern API

Principles

  • fullstack support with consistent behavior and output
  • no synonyms, only one way to do anything
  • no test runner required, pure node/browser
  • async/await for flow control
  • no configuration files
  • control behavior with env vars, never with options objects

API

test(description, async function) (default)

Create a test, the async function will be passed an object, which provides the assertions and utilities interface.

const { test } = require('aquatap')
test('some test', async ({ is }) => {
  is(true, true)
})

For convenience the test method is both the main export and named exported method

const test = require('aquatap')
console.log(test === test.test) // true

only(description, async function)

Filter out other tests by using the only method:

const { test, only } = require('aquatap')
test('some test', async ({ is }) => {
  is(true, true)
})
only('another test', async ({ is }) => {
  is(true, false)
})

When run normally, this will run both tests, when when run with the TAP_ONLY environment variable only the second test will run:

TAP_ONLY=1 node test.js # will only run the second test

The only method is also available on the test method:

const { test } = require('aquatap') 
test.only('another test', async ({ is }) => {
  is(true, false)
})

skip(description, async function)

Skip a test:

const { test, skip } = require('aquatap')
skip('some test', async ({ is }) => {
  is(true, true)
})
test('another test', async ({ is }) => {
  is(true, false)
})

The first test will not be executed.

The skip method is also available on the test method:

const { test } = require('aquatap') 
test.skip('some test', async ({ is }) => {
  is(true, true)
})

pipe(stream)

By default TAP output is streamed to process.stdout. Use the pipe method to redirect it to any writable stream.

Assertions

is(actual, expected, [ message ])

Compare actual to expected with ===

isNot(actual, expected, [ message ])

Compare actual to expected with !==

same(actual, expected, [ message ])

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

different(actual, expected, [ message ])

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

throws(function|async function, [ error, message ])

Verify that a function throws.

throws(() => { throw Error('an err') }, Error('an err'))

If the function is an async function, throws must be awaited:

await throws(async () => { throw Error('an err')}, Error('an err'))

Where the error parameter is supplied as an error instance or as a string representing the error message, the thrown (or rejected) error will be checked against it.

doesNotThrow(function|async function, [ message ])

Verify that a function or async function does not throw.

If the function is an async function, doestNotThrow must be awaited:

await doesNotThrow(async () => { })

ok(value, [ message ])

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

not(value, [ message ])

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

pass([ message ])

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

fail([ message ])

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

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

Compare actual to expected with ==

isNot.loosely(actual, expected, [ message ])

Compare actual to expected with !=

same.loosely(actual, expected, [ message ])

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

different.loosely(actual, expected, [ message ])

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

Utilities

plan(n)

Constrain a test to an explicit amount of assertions.

timeout(ms)

Fail the test after a given timeout.

comment(message)

Inject a TAP comment into the output.

Running Tests

Node

Aquatap is a test library, rather than a test runner. For running Node tests, there are no globally injected variables or anything special required so tests can be run directly with node like so:

node my-tests.js

This will output TAP to stdout. A test runner can be used to make tests prettier and generate coverage information. As a convenience aquatap exposes the tap executable of tap for various TAP processing and reporting:

tap my-tests.js

Coverage

The tap executable can be used to analyze coverage like so:

tap --coverage my-tests.js

Use tap --help to discover more options.

Browser

Aquatap has been built to work with airtap.

Local test running will work out of the box:

airtap --local my-tests.js

See the airtap docs for configuring with Sauce Labs, running in Electron and more.

Configuration

Configuration is achieved via environment variables.

TAP_ONLY

Restrict tests to those using the only method instead of the test method.

Strategies

Testing callback-based API's

Tests must only be specified using async functions, any callback API's have to be shoehorned into the async/await paradigm.

For common callback scenarios the promisify method on the core util module can be used to achieve this:

const test = require('aquatap')
const { promisify } = require('util')
const dns = require('dns')
test('a callback-based API', async ({ same }) => {
  const lookup = promisify(dns.lookup)
  same(await lookup('google.com'), { address: '172.217.19.238', family: 4 })
})

An added advantage of this approach is that any errors will lead to promise rejection, which will lead to a throw in the async function, which is then automatically handled as a TAP assertion failure in the output.

Testing events

While event emitters do use callbacks, the callbacks are generally designed to be called multiple times. This is also true of API's with listener functions, such as the createServer method of http. Typically listener function parameters are passed onto event emitters.

To test an event use the events.once method:

const test = require('aquatap')
const { once } = require('events')
const { createServer, get } = require('http')
const test = require('.')
test('an event-based API', async ({ is }) => {
  const server = createServer((req, res) => res.end('test')).unref().listen()
  await once(server, 'listening')
  const { port } = server.address()
  const [ res ] = await once(get(`http://localhost:${port}`), 'response')
  const [ result ] = await once(res, 'data')
  is(result.toString(), 'test')
})

Prior Art

Aquatap is based on both tape and tap, and wraps both of them to create a consistent API across browser and Node.

License

MIT

2.0.0

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago