Lightweight testing library for Node.js
test
, describe
, testSuite
async
/await
2.3 kB
npm i -D manten
test()
/describe()
run on creationasync
/await
/Promise API for async flow controldescribe()
groups by inheriting a new the describe
functionexpect
assertion library is re-exported1
// tests/test.mjs
import { describe, expect } from 'manten'
describe('My test suite', ({ test, describe }) => {
test('My test', () => {
expect(true).toBe(true)
})
describe('Nested groups', ({ test }) => {
// ...
})
})
Run the test file with Node.js:
node tests/test.mjs
Create and run a test with the test(name, testFunction)
function. The first argument is the test name, and the second is the test function. Optionally, you can pass in a timeout in milliseconds as the third argument for asynchronous tests.
The test runs immediately after the test function is invoked and the results are logged to stdout.
import { test, expect } from 'manten'
test('Test A', () => {
expect(somethingSync()).toBe(1)
})
// Runs after Test A completes
test('Test B', () => {
expect(somethingSync()).toBe(2)
})
Jest's expect
is exported as the default assertion library. Read the docs here.
import { expect } from 'manten'
Feel free to use a different assertion library, such as Node.js Assert or Chai.
Group tests with the describe(description, testGroupFunction)
function. The first parameter is the description of the group, and the second is the test group function.
Note, the test
function is no longer imported but is passed as an argument to the test group function. This helps keep track of async contexts and generate better output logs, which we'll get into later.
import { describe } from 'manten'
describe('Group description', ({ test }) => {
test('Test A', () => {
// ...
})
test('Test B', () => {
// ...
})
})
describe()
groups are infinitely nestable by using the new describe
function from the test group function.
describe('Group description', ({ test, describe }) => {
test('Test A', () => {
// ...
})
// ...
describe('Nested group', ({ test }) => {
// ...
})
})
Test async code by passing in an async
function to test()
.
Control the flow of your tests via native async
/await
syntax or Promise API.
Run asynchronous tests sequentially by await
ing on each test.
Node.js v14.8 and up supports top-level await. Alternatively, wrap the whole block in an async IIFE.
await test('Test A', async () => {
await somethingAsync()
})
// Runs after Test A completes
await test('Test B', async () => {
await somethingAsync()
})
Run tests concurrently by running them without await
.
test('Test A', async () => {
await somethingAsync()
})
// Runs while "Test A" is running
test('Test B', async () => {
await somethingAsync()
})
Pass in the max time duration a test can run for as the third argument to test()
.
test('should finish within 1s', async () => {
await slowTest()
}, 1000)
All descendant tests in a describe()
are collected so await
ing on the describe()
will wait for all async tests inside to complete, even if they are not individually await
ed.
To run tests inside describe()
sequentially, pass in an async function to describe()
and use await
on each test.
await describe('Group A', ({ test }) => {
test('Test A1', () => {
// ...
})
// Test A2 will run concurrently with A1
test('Test A2', () => {
// ...
})
})
// Will wait for all tests in Group A to finish
test('Test B', () => {
// ...
})
Group tests into separate files by exporting a testSuite()
. This can be useful for organization, or creating a set of reusable tests since test suites can accept arguments.
// test-suite-a.ts
import { testSuite } from 'manten'
export default testSuite((
{ describe, test },
// Can have parameters to accept
value: number
) => {
test('Test A', async () => {
// ...
})
describe('Group B', ({ test }) => {
// ...
})
})
import testSuiteA from './test-suite-a'
// Pass in a value to the test suite
testSuiteA(100)
Nest test suites with the describe()
function by calling it with runTestSuite(testSuite)
. This will log all tests in the test suite under the group description.
import { describe } from 'manten'
describe('Group', ({ runTestSuite }) => {
runTestSuite(import('./test-suite-a'))
})
onTestFail
By using the onTestFail
hook, you can debug tests by logging relevant information when a test fails.
test('Test', async ({ onTestFail }) => {
const fixture = await createFixture()
onTestFail(async (error) => {
console.log(error)
console.log('inspect directory:', fixture.path)
})
throw new Error('Test failed')
})
onTestFinish
By using the onTestFinish
hook, you can execute cleanup code after the test finishes, even if it errors.
test('Test', async ({ onTestFinish }) => {
const fixture = await createFixture()
onTestFinish(async () => await fixture.remove())
throw new Error('Test failed')
})
onFinish
Similarly to onTestFinish
, you can execute cleanup code after all tests in a describe()
finish.
describe('Describe', ({ test, onFinish }) => {
const fixture = await createFixture()
onFinish(async () => await fixture.remove())
test('Check fixture', () => {
// ...
})
})
import getNode from 'get-node'
import { execaNode } from 'execa'
import { testSuite } from 'manten'
const runTest = testSuite((
{ test },
node: { path: string; version: string }
) => {
test(
`Works in Node.js ${node.version}`,
() => execaNode('./script.js', { nodePath: node.path })
)
});
['12.22.9', '14.18.3', '16.13.2'].map(
async nodeVersion => runTest(await getNode(nodeVersion))
)
name: string
testFunction: () => void
timeout: number
Return value: Promise<void>
Create and run a test.
description: string
testGroupFunction: ({ test, describe, runTestSuite }) => void
Return value: Promise<void>
Create a group of tests.
testSuiteFunction: ({ test, describe, runTestSuite }) => any
testSuiteArguments: any[]
Return value: (...testSuiteArguments) => Promise<ReturnType<testSuiteFunction>>
Create a test suite.
Manten (まんてん, 満点) means "maximum points" or 100% in Japanese.
It's a Hanamaru symbol:
The hanamaru (はなまる, 花丸) is a variant of the O mark used in Japan, written as 💮︎. It is typically drawn as a spiral surrounded by rounded flower petals, suggesting a flower. It is frequently used in praising or complimenting children, and the motif often appears in children's characters and logos.
The hanamaru is frequently written on tests if a student has achieved full marks or an otherwise outstanding result. It is sometimes used in place of an O mark in grading written response problems if a student's answer is especially good. Some teachers will add more rotations to the spiral the better the answer is. It is also used as a symbol for good weather.
— https://en.wikipedia.org/wiki/O_mark
Currently, Manten does not come with a test runner because the tradeoffs are not worh it.
The primary benefit of the test runner is that it can detect and run all test files, and maybe watch for file changes to re-run tests.
The drawbacks are:
beforeAll
/beforeEach
hooks usually deal with managing shared environmental variables.
Since Manten puts async flows first, this paradigm doesn't work well when tests are running concurrently.
By creating a new context for each test, a more functional approach can be taken where shared logic is better abstracted and organized.
Before
There can be a lot of code between the beforeEach
and the actual tests that makes it hard to follow the flow in logic.
beforeAll(async () => {
doSomethingBeforeAll()
})
beforeEach(() => {
doSomethingBeforeEach()
})
// There can be a lot of code in between, making
// it hard to see there's logic before each test
test('My test', () => {
// ...
})
After
Less magic, more explicit code!
await doSomethingBeforeAll()
test('My test', async () => {
await doSomethingBeforeEach()
// ...
})
Easily create test fixtures at a temporary file-system path.