tehanu
TypeScript icon, indicating that this package has built-in type declarations

1.0.1 • Public • Published

Test Harness Core

NPM version

Provides a framework for creation and execution of test suites.

Synopsis

Install and run

test.cjs:

const test = require('tehanu')(__filename)

test('dummy', () => {})

test.mjs:

import tehanu from 'tehanu'

const test = tehanu('sum')

test('dummy', () => {})

test.mjs (unnamed suite):

import test from 'tehanu/suite'

test('dummy', () => {})

Installation

You can install the test harness using your favourite Node.js package manager:

npm i -D tehanu
yarn add -D tehanu
pnpm i -D tehanu

Test Suite

The default export of the tehanu module:

const tehanu = require('tehanu')

is a test suite factory function:

tehanu(name: string): (...) => void

If the name of the suite is an absolute path and it starts with the current process path (process.cwd()), usually supplied as __filename, the leading current process path will be trimmed and only the relative path to the current process path will be used. If you want to trim all the path and leave just the file name, you can use the expression require('path').basename(__filename) instead of just __filename. The same will work with import.meta.url in ES modules.

Calling test suite factory function will create a new test suite:

const test = tehanu(__filename)

and return a test factory function:

test(name: string, fn: () => void | Promise): void

Calling the test factory function will add another test to the test suite:

test('one number', () => ...)

A test suite is usually alone in a single test file:

const test = require('tehanu')(__filename)

test('one number', () => ...)

But a single test file may include more test suites:

const tehanu = require('tehanu')
const test1 = tehanu('add')
const test2 = tehanu('remove')

test1('add one', () => ...)
test2('remove one', () => ...)

Multiple source files with test suites can be executed either by teru - the test runner, or by creating a test suite index, which can be executed by node:

require('./sum1.test')
require('./sum2.test')

ESM modules are supported too:

import tehanu from 'tehanu'

const test = tehanu(import.meta.url)

test('one number', () => ...)

with a shortcut to an unnamed test suite creation:

import test from 'tehanu/suite'

test('one number', () => ...)

Setup and Tear Down

The test factory function carries properties exposing methods for adding callbacks to execute before and after the tests:

// Adds callbacks to execute before and after all tests in a test suite
before(fn: () => void | Promise): void
after(fn: () => void | Promise): void
// Adds callbacks to execute before and after each test in a test suite
beforeEach(fn: () => void | Promise): void
beforeEach(fn: () => void | Promise): void

The callbacks can be registered anywhere in the source file and will be executed in the declared order:

const test = require('tehanu')(__filename)

test.before(() => ...)
test('one number', () => ...)

Limiting and Skipping Tests

The test factory function carries properties exposing methods for limiting the test execution only to selected tests and to skip selected tests:

// Adds the test to a limited set of tests to execute
only(name: string, fn?: () => void | Promise): void
// Removes the test from the set of tests to execute
skip(name: string, fn: () => void | Promise): void

The .only suffix can be temporarily inserted to the test sources to limit the test execution only to a few tests to make debugging easier. The .skip suffix can be temporarily inserted to the test sources to exclude the execution of some tests until they are fixed:

const test = require('tehanu')(__filename)

test.only('one number', () => ...)
test.skip('two numbers', () => ...)

Test Execution

Tests are executed automatically once all tests suites get loaded. It is ensured by starting the execution from a setImmediate callback. All tests and test suites have to be declared synchronously in test sources.

If you want to suppress the automatic test execution, set the autostart property in package.json to false:

"tehanu": {
  "autostart": false
}

If your tests run in the browser, set the autostart property on the global tehanu object to false:

(window.tehanu || (window.tehanu = {}).autostart = false

The loaded tests can be executed manually using either the schedule or the run method:

const { schedule, run } = require('tehanu')

interface Options {
  reporter?: Reporter,
  bail?: boolean,
  parallel?: boolean,
  parallelSuites?: boolean
}

schedule(options?: Options): void
run(options?: Options): Promise

The method schedule ensures that the test suites are processed just once, even if it is called multiple times. It also reads the default options from the tehanu object in package.json, or in the browser. It also works well together with teru. The method run executes the test suites right away and is not compatible with teru.

The tehanu object in package.json, or in the browser, or the options for the schedule and run methods can contain the following additional properties to control the test run:

  • bail: if set to true, the test execution will be aborter after the first failure occurs.
  • parallel: if set to true, the test suites will be executed concurrently.
  • parallelSuites: if set to true, the tests within one test suite will be executed concurrently.

If a test fails, the exit code of the process running the tests will be set to 1.

Reporters

No reporter is used by default, which means that only errors thrown from the failed tests will be printed on the console. A reporter can print more information about the test progress and can be provided by a couple of ways.

Declarative

If you install one or multiple reporters as dependencies, the first one will be chosen automatically:

"devDependencies": {
  "tehanu": "1.0.0",
  "tehanu-repo-coco": "1.0.0"
}

If you install multiple reporters, you can choose one of them explicitly:

"tehanu": {
  "reporter": "tehanu-repo-tape"
},
"devDependencies": {
  "tehanu": "1.0.0",
  "tehanu-repo-coco": "1.0.0",
  "tehanu-repo-tape": "1.0.0"
}

If you run the tests in the browser, you can supply the reporter by the global tehanu object:

<script src=node_modules/tehanu/dist/index.min.js></script>
<script src=node_modules/tehanu-repo-tape/dist/index.min.js></script>
<script src=test/index.js></script>
<script>
  (window.tehanu || (window.tehanu = {}).reporter = tehanuTape
</script>
<script type=module>
  import tape from 'node_modules/tehanu-repo-tape/dist/index.min.mjs'
  import 'test/dist/index.js'

  (window.tehanu || (window.tehanu = {}).reporter = tape
</script>

But you do not have to do it with tape, because this reporter registers to the tehanu object automatically for convenience:

<script src=node_modules/tehanu/dist/index.min.js></script>
<script src=node_modules/tehanu-repo-tape/dist/index.min.js></script>
<script src=test/index.js></script>
<script type=module>
  import 'node_modules/tehanu-repo-tape/dist/index.min.mjs'
  import 'test/dist/index.js'
</script>

Programmatic

If you execute the functions schedule or run yourself, you can pass a reporter object to them:

const { factory, schedule } = require('tehanu')
const test = factory('sum')

test('dummy', () => {})

const reporter = {
  start() {},
  startSuite() {},
  ok({ name }) { console.info(`DONE: ${name} `) },
  fail({ name }, { stack }) {
    console.error(`FAIL: ${name}

${stack}`)
  },
  bail() {},
  skip() {},
  endSuite() {},
  end() {}
}

schedule({ reporter })

A reporter has to implement the Reporter interface:

export interface Test {
  readonly name: string
  readonly fn?: () => void | Promise
}

interface Reporter {
  start(): void
  startSuite(name: string, tests: Test[]): void
  ok(test: Test): void
  fail(test: Test, err: Error): void
  bail(): void
  skip(test: Test): void
  endSuite(): void
  end(): void
}

Package Sidebar

Install

npm i tehanu

Weekly Downloads

9

Version

1.0.1

License

MIT

Unpacked Size

38.2 kB

Total Files

14

Last publish

Collaborators

  • prantlf