A low-feature, dependency-free and performant test runner inspired by Rust and Deno
- Simplicity: Use the
mt
test runner with thetest
function, nothing more. - Performance: By doing and including less we can run more quick.
- Minimal: Bring your own assertions, snapshots,
mt
will always be dependency free.
Table of Contents
-
Install via your tool of choice:
pnpm add --dev @sondr3/minitest
-
Add a test script to
package.json
:"scripts": { "test": "mt <dir>", },
-
Write your tests:
import { strict as assert } from "assert"; import { test } from "@sondr3/minitest"; test("it works!", () => { assert(true === true, "Phew"); });
-
Run your tests:
pnpm test
running 1 test running 1 test in dir/index.test.js test it works! ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 filtered out; finished in 32ms
mt
will recursively look for files in your current directory:
- named
test.{js,mjs}
, - or ending in
.test.{js,mjs}
- or ending in
_test.{js,mjs}
To write tests you need to import the test
function from @sondr3/minitest
. There
are a couple of different styles that can be used to write tests, but we'll get
into these later.
import { test } from "@sondr3/minitest";
import { strict as assert } from "node:assert";
test("hello world #1", () => {
const x = 1 + 2;
assert.equal(x, 3);
});
test({ name: "hello world #2" }, () => {
const x = 1 + 2;
assert.equal(x, 3);
});
You can use any assertion library you want, like Chai for a TDD/BDD like assertions and Sinon.JS to spy and mock functionality. The only requirement is that the assertions throw when they fail.
If you come from a frameworks like Jest you may be surprised to learn
that there is no built-in support for nesting tests, or anything like beforeAll
or beforeEach
in minitest
. I can highly recommend this article from
Kent C. Dodds about nesting and test hooks. After having used Deno and Rust which do
not have such functionality, I don't miss it. I recommend you give it a try!
You can also test asynchronous code by turning the test function into a function
that returns a promise. Simply add async
in front of the function:
import { test } from "@sondr3/minitest";
import { strict as assert } from "node:assert";
test("async hello world", async () => {
const x = 1 + 2;
const wait = async () => new Promise((res) => setTimeout(res, 1000));
await wait();
assert.equal(x, 3);
});
Simply run mt
to test all test files matching the pattern in finding tests, or
point it to the directory or file you want to run:
# Run all tests in current directory
mt
# Run all tests in the fs directory
mt fs
# Run a single test file
mt fs.test.js
There are tree different ways of filtering tests, either by filtering against test names, skipping tests or running a select subset of tests.
Tests can be filtered out by using the --filter
option. This option accepts
either a case insensitive string or a Regex pattern as its value.
If you have the following tests:
test("a-test", () => {});
test("test-1", () => {});
test("test-2", () => {});
You can then filter them by filtering all tests that contain the word test
:
mt --filter "test"
Or by matching against a Regex pattern:
mt --filter "/test-*\d/"
If you want to ignore/skip tests based on some boolean condition or because they
are currently incorrect you can use the ignore()
method or the ignore
field
in the options:
test("ignored", () => {}).ignore(); // or .ignore(condition)
test({ name: "other ignored", ignore: process.arch === "x64" }, () => {});
test("final ignored", () => {}, { ignore: true });
Sometimes you may want to only run a subset of your tests because you are working
on a problem where you want to ignore tests not relevant to it. To do this you can
mark tests using the only()
method or the only
field in the options, and only
these tests are run. While these tests will run normally and report success and
failure normally, the whole test suite will still fail as using only
should only
be a temporary measure as it disables nearly the whole test suite.
test("other ignored", () => {}).only();
test({ name: "ignored", only: true }, () => {});
test("final ignored", () => {}, { only: true });
If you have a test suite that takes a long time to complete or you simply want
to exit after the first error, one can use the --fail-fast
option:
# fail immediately
mt --fail-fast
# fail after three failures
mt --fail-fast 3
The test output for mt
can quickly become verbose once the test suite grows, to
help with this one can use the --quiet
flag to make it far less verbose:
mt --quiet
For a complete overview over the available options, use the --help
flag:
$ mt --help
minitest v0.1.0
A low-feature and performant test runner inspired by Rust and Deno
USAGE:
mt <dir> [flags]
OPTIONS:
-q, --quiet Quiet output
-f, --filter=<filter> Filter tests by name, accepts regex
-F, --fail-fast=<N> Fail after N test failures [default: 0]
-v, --version Print version
-h, --help Print help
There is no built-in support for natively running TypeScript files, they need to be compiled to JavaScript first. In other words, your build step needs to happen before you run your tests.
To generate coverate reporting for your tests, I highly recommend using c8. It
generates coverage based on the built-in coverage functionality of NodeJS and will
just magically work with your tests. It is also very fast compared to similar
functionality in other testing libraries. To generate coverage by default, change
your test
script to the following after you've added c8 as a dependency:
"scripts": {
"test": "c8 mt <dir>",
},
Like with TypeScript, there is no built-in support for usage with JSX and/or TSX, and no specific functionality for working against browsers. You may be able to make it work, but it is purpose built around testing with Node, so YMMV.
Why would you use this over the myriad of other test frameworks, runners and libraries that exist? You probably shouldn't, Jest, Ava, tape, uvu and a whole lot of others are more mature, stable, feature rich and has a wider userbase, but if you want a simple Rust/Deno like test framework, this might just scratch your itch.
I also created this as an exercise in learning how testing works under the hood, and had
a lot of fun building it out into a actual useful package. For my needs and packages minitest
is just what I want from a test framework: minimal fuzzing, fast and easy and straight forward.
MIT.