Futuristic test runner
Even though JavaScript is single-threaded, IO in Node.js can happen in parallel due to its async nature. AVA takes advantage of this and runs your tests concurrently, which is especially beneficial for IO heavy tests. In addition, test files are run in parallel as separate processes, giving you even better performance and an isolated environment for each test file. Switching from Mocha to AVA in Pageres brought the test time down from 31 sec to 11 sec. Having tests run concurrently forces you to write atomic tests, meaning tests don't depend on global state or the state of other tests, which is a great thing!
Read our contributing guide if you're looking to contribute (issues/PRs/etc).
Follow the AVA Twitter account for updates.
Translations: Español, Français, Italiano, 日本語, 한국어, Português, Русский, 简体中文
Table of Contents
Why AVA?
Test syntax
import test from 'ava';
test(t => {
t.deepEqual([1, 2], [1, 2]);
});
Usage
Add AVA to your project
Install AVA globally run with --init
to add AVA to your package.json
:
$ npm install --global ava
$ ava --init
{
"name": "awesome-package",
"scripts": {
"test": "ava"
},
"devDependencies": {
"ava": "^0.11.0"
}
}
Any arguments passed after --init
are added in the package.json
.
Manual installation
You can also install AVA directly:
$ npm install --save-dev ava
You'll have to configure the test
script in your package.json
to use ava
(see above).
Create your test file
Create a file named test.js
in the project root directory:
import test from 'ava';
test('foo', t => {
t.pass();
});
test('bar', async t => {
const bar = Promise.resolve('bar');
t.is(await bar, 'bar');
});
Run it
$ npm test
Watch it
$ npm test -- --watch
AVA comes with an intelligent watch mode. Learn more in its recipe.
CLI
$ ava --help
Usage
ava [<file|directory|glob> ...]
Options
--init Add AVA to your project
--fail-fast Stop after first test failure
--serial, -s Run tests serially
--require, -r Module to preload (Can be repeated)
--tap, -t Generate TAP output
--verbose, -v Enable verbose output
--no-cache Disable the transpiler cache
--match, -m Only run tests with matching title (Can be repeated)
--watch, -w Re-run tests when tests and source files change
--source, -S Pattern to match source files so tests can be re-run (Can be repeated)
--timeout, -T Set global timeout
--concurrency, -c Maximum number of test files running at the same time (EXPERIMENTAL)
Examples
ava
ava test.js test2.js
ava test-*.js
ava test
ava --init
ava --init foo.js
Default patterns when no arguments:
test.js test-*.js test/**/*.js **/__tests__/**/*.js **/*.test.js
Note that the CLI will use your local install of AVA when available, even when run globally.
Directories are recursed, with all *.js
files being treated as test files. Directories named fixtures
, helpers
and node_modules
are always ignored. So are files starting with _
which allows you to place helpers in the same directory as your test files.
When using npm test
, you can pass positional arguments directly npm test test2.js
, but flags needs to be passed like npm test -- --verbose
.
Configuration
All of the CLI options can be configured in the ava
section of your package.json
. This allows you to modify the default behavior of the ava
command, so you don't have to repeatedly type the same options on the command prompt.
{
"ava": {
"files": [
"my-test-folder/*.js",
"!**/not-this-file.js"
],
"source": [
"**/*.{js,jsx}",
"!dist/**/*"
],
"match": [
"*oo",
"!foo"
],
"failFast": true,
"tap": true,
"require": [
"babel-register"
],
"babel": "inherit"
}
}
Arguments passed to the CLI will always take precedence over the configuration in package.json
.
See the ES2015 support section for details on the babel
option.
Documentation
Tests are run concurrently. You can specify synchronous and asynchronous tests. Tests are considered synchronous unless you return a promise or observable.
We highly recommend the use of async functions. They make asynchronous code concise and readable, and they implicitly return a promise so you don't have to.
If you're unable to use promises or observables, you may enable "callback mode" by defining your test with test.cb([title], fn)
. Tests declared this way must be manually ended with t.end()
. This mode is mainly intended for testing callback-style APIs.
You must define all tests synchronously. They can't be defined inside setTimeout
, setImmediate
, etc.
Test files are run from their current directory, so process.cwd()
is always the same as __dirname
. You can just use relative paths instead of doing path.join(__dirname, 'relative/path')
.
Creating tests
To create a test you call the test
function you imported from AVA. Provide the optional title and implementation function. The function will be called when your test is run. It's passed an execution object as its first and only argument. By convention this argument is named t
.
import test from 'ava';
test('my passing test', t => {
t.pass();
});
Titles
Titles are optional, meaning you can do:
test(t => {
t.pass();
});
It's recommended to provide test titles if you have more than one test.
If you haven't provided a test title, but the implementation is a named function, that name will be used as the test title:
test(function name(t) {
t.pass();
});
Assertion planning
Assertion plans ensure tests only pass when a specific number of assertions have been executed. They'll help you catch cases where tests exit too early. They'll also cause tests to fail if too many assertions are executed, which can be useful if you have assertions inside callbacks or loops.
Note that, unlike tap
and tape
, AVA does not automatically end a test when the planned assertion count is reached.
These examples will result in a passed test:
test(t => {
t.plan(1);
return Promise.resolve(3).then(n => {
t.is(n, 3);
});
});
test.cb(t => {
t.plan(1);
someAsyncFunction(() => {
t.pass();
t.end();
});
});
These won't:
test(t => {
t.plan(2);
for (let i = 0; i < 3; i++) {
t.true(i < 3);
}
});
test(t => {
t.plan(1);
someAsyncFunction(() => {
t.pass();
});
});
Running tests serially
By default tests are run concurrently, which is awesome. Sometimes though you have to write tests that cannot run concurrently.
In these rare cases you can use the .serial
modifier. It'll force those tests to run serially before the concurrent ones.
test.serial(t => {
t.pass();
});
Note that this only applies to tests within a particular test file. AVA will still run multiple tests files at the same time unless you pass the --serial
CLI flag.
Running specific tests
During development it can be helpful to only run a few specific tests. This can be accomplished using the .only
modifier:
test('will not be run', t => {
t.fail();
});
test.only('will be run', t => {
t.pass();
});
.only
applies across all test files, so if you use it in one file, no tests from the other file will run.
Running tests with matching titles
The --match
flag allows you to run just the tests that have a matching title. This is achieved with simple wildcard patterns. Patterns are case insensitive. See matcher
for more details.
Match titles ending with foo
:
$ ava --match='*foo'
Match titles starting with foo
:
$ ava --match='foo*'
Match titles containing foo
:
$ ava --match='*foo*'
Match titles that are exactly foo
(albeit case insensitively):
$ ava --match='foo'
Watch titles not containing foo
:
$ ava --match='!*foo*'
Match titles starting with foo
and ending with bar
:
$ ava --match='foo*bar'
Match titles starting with foo
or ending with bar
:
$ ava --match='foo*' --match='*bar'
Note that a match pattern takes precedence over the .only
modifier. Only tests with an explicit title are matched. Tests without titles or whose title is derived from the implementation function will be skipped when --match
is used.
Here's what happens when you run AVA with a match pattern of *oo*
and the following tests:
test('foo will run', t => {
t.pass();
});
test('moo will also run', t => {
t.pass();
});
test.only('boo will run but not exclusively', t => {
t.pass();
});
test(function (t) {
t.fail();
});
test(function foo(t) {
t.fail();
});
Skipping tests
Sometimes failing tests can be hard to fix. You can tell AVA to skip these tests using the .skip
modifier. They'll still be shown in the output (as having been skipped) but are never run.
test.skip('will not be run', t => {
t.fail();
});
You must specify the implementation function.
Test placeholders ("todo")
You can use the .todo
modifier when you're planning to write a test. Like skipped tests these placeholders are shown in the output. They only require a title; you cannot specify the implementation function.
test.todo('will think about writing this later');
Failing tests
You can use the .failing
modifier to document issues with your code that need to be fixed. Failing tests are run just like normal ones, but they are expected to fail, and will not break your build when they do. If a test marked as failing actually passes, it will be reported as an error and fail the build with a helpful message instructing you to remove the .failing
modifier.
This allows you to merge .failing
tests before a fix is implemented without breaking CI. This is a great way to recognize good bug report PR's with a commit credit, even if the reporter is unable to actually fix the problem.
test.failing('demonstrate some bug', t => {
t.fail();
});
Before & after hooks
AVA lets you register hooks that are run before and after your tests. This allows you to run setup and/or teardown code.
test.before()
registers a hook to be run before the first test in your test file. Similarly test.after()
registers a hook to be run after the last test. Use test.after.always()
to register a hook that will always run once your tests and other hooks complete. .always()
hooks run regardless of whether there were earlier failures, so they are ideal for cleanup tasks.
test.beforeEach()
registers a hook to be run before each test in your test file. Similarly test.afterEach()
a hook to be run after each test. Use test.afterEach.always()
to register an after hook that is called even if other test hooks, or the test itself, fail. .always()
hooks are ideal for cleanup tasks.
Note: If the --fail-fast
flag is specified, AVA will stop after the first test failure and the .always
hook will not run.
Like test()
these methods take an optional title and a callback function. The title is shown if your hook fails to execute. The callback is called with an execution object.
before
hooks execute before beforeEach
hooks. afterEach
hooks execute before after
hooks. Within their category the hooks execute in the order they were defined.
test.before(t => {
});
test.before(t => {
});
test.after('cleanup', t => {
});
test.after.always('guaranteed cleanup', t => {
});
test.beforeEach(t => {
});
test.afterEach(t => {
});
test.afterEach.always(t => {
});
test(t => {
});
Hooks can be synchronous or asynchronous, just like tests. To make a hook asynchronous return a promise or observable, use an async function, or enable callback mode via test.cb.before()
, test.cb.beforeEach()
etc.
test.before(async t => {
await promiseFn();
});
test.after(t => {
return new Promise();
});
test.cb.beforeEach(t => {
setTimeout(t.end);
});
test.afterEach.cb(t => {
setTimeout(t.end);
});
Keep in mind that the beforeEach
and afterEach
hooks run just before and after a test is run, and that by default tests run concurrently. If you need to set up global state for each test (like spying on console.log
for example), you'll need to make sure the tests are run serially.
Remember that AVA runs each test file in its own process. You may not have to clean up global state in a after
-hook since that's only called right before the process exits.
The beforeEach
& afterEach
hooks can share context with the test:
test.beforeEach(t => {
t.context.data = generateUniqueData();
});
test(t => {
t.is(t.context.data + 'bar', 'foobar');
});
By default t.context
is an object but you can reassign it:
test.beforeEach(t => {
t.context = 'unicorn';
});
test(t => {
t.is(t.context, 'unicorn');
});
Context sharing is not available to before
and after
hooks.
Chaining test modifiers
You can use the .serial
, .only
and .skip
modifiers in any order, with test
, before
, after
, beforeEach
and afterEach
. For example:
test.before.skip(...);
test.skip.after(...);
test.serial.only(...);
test.only.serial(...);
This means you can temporarily add .skip
or .only
at the end of a test or hook definition without having to make any other changes.
Test macros
Additional arguments passed to the test declaration will be passed to the test implementation. This is useful for creating reusable test macros.
function macro(t, input, expected) {
t.is(eval(input), expected);
}
test('2 + 2 === 4', macro, '2 + 2', 4);
test('2 * 3 === 6', macro, '2 * 3', 6);
You can build the test title programmatically by attaching a title
function to the macro:
function macro(t, input, expected) {
t.is(eval(input), expected);
}
macro.title = (providedTitle, input, expected) => `${providedTitle} ${input} === ${expected}`.trim();
test(macro, '2 + 2', 4);
test(macro, '2 * 3', 6);
test('providedTitle', macro, '3 * 3', 9);
The providedTitle
argument defaults to an empty string if the user does not supply a string title. This allows for easy concatenation without having to worry about null
/ undefined
. It is worth remembering that the empty string is considered a falsy value, so you can still use if(providedTitle) {...}
.
You can also pass arrays of macro functions:
const safeEval = require('safe-eval');
function evalMacro(t, input, expected) {
t.is(eval(input), expected);
}
function safeEvalMacro(t, input, expected) {
t.is(safeEval(input), expected);
}
test([evalMacro, safeEvalMacro], '2 + 2', 4);
test([evalMacro, safeEvalMacro], '2 * 3', 6);
We encourage you to use macros instead of building your own test generators (here is an example of code that should be replaced with a macro). Macros are designed to perform static analysis of your code, which can lead to better performance, IDE integration, and linter rules.
Custom assertions
You can use any assertion library instead of or in addition to the built-in one, provided it throws exceptions when the assertion fails.
This won't give you as nice an experience as you'd get with the built-in assertions though, and you won't be able to use the assertion planning (see #25).
import assert from 'assert';
test(t => {
assert(true);
});
ES2015 support
AVA comes with built-in support for ES2015 through Babel 6. Just write your tests in ES2015. No extra setup needed. You can use any Babel version in your project. We use our own bundled Babel with the es2015
and stage-2
presets, as well as the espower
and transform-runtime
plugins.
The corresponding Babel config for AVA's setup is as follows:
{
"presets": [
"es2015",
"stage-2"
],
"plugins": [
"espower",
"transform-runtime"
]
}
You can customize how AVA transpiles the test files through the babel
option in AVA's package.json
configuration. For example to override the presets you can use:
{
"ava": {
"babel": {
"presets": [
"es2015",
"stage-0",
"react"
]
}
},
}
You can also use the special "inherit"
keyword. This makes AVA defer to the Babel config in your .babelrc
or package.json
file. This way your test files will be transpiled using the same config as your source files without having to repeat it just for AVA:
{
"babel": {
"presets": [
"es2015",
"stage-0",
"react"
]
},
"ava": {
"babel": "inherit"
},
}
See AVA's .babelrc
recipe for further examples and a more detailed explanation of configuration options.
Note that AVA will always apply a few internal plugins regardless of configuration, but they should not impact the behavior of your code.
TypeScript support
AVA includes typings for TypeScript. You have to set up transpilation yourself. When you set module
to commonjs
in your tsconfig.json
file, TypeScript will automatically find the type definitions for AVA. You should set target
to es2015
to use promises and async functions.
Transpiling imported modules
AVA currently only transpiles the tests you ask it to run. It will not transpile modules you import
from outside of the test. This may be unexpected but there are workarounds.
If you use Babel you can use its require hook to transpile imported modules on-the-fly. Run AVA with --require babel-register
(see CLI) or configure it in your package.json
.
You can also transpile your modules in a separate process and refer to the transpiled files rather than the sources from your tests.
Promise support
If you return a promise in the test you don't need to explicitly end the test as it will end when the promise resolves.
test(t => {
return somePromise().then(result => {
t.is(result, 'unicorn');
});
});
Generator function support
AVA comes with built-in support for generator functions.
test(function * (t) {
const value = yield generatorFn();
t.true(value);
});
Async function support
AVA comes with built-in support for async functions (async/await).
test(async function (t) {
const value = await promiseFn();
t.true(value);
});
test(async t => {
const value = await promiseFn();
t.true(value);
});
Observable support
AVA comes with built-in support for observables. If you return an observable from a test, AVA will automatically consume it to completion before ending the test.
You do not need to use "callback mode" or call t.end()
.
test(t => {
t.plan(3);
return Observable.of(1, 2, 3, 4, 5, 6)
.filter(n => {
return n % 2 === 0;
})
.map(() => t.pass());
});
Callback support
AVA supports using t.end
as the final callback when using node-style error-first callback APIs. AVA will consider any truthy value passed as the first argument to t.end
to be an error. Note that t.end
requires "callback mode", which can be enabled by using the test.cb
chain.
test.cb(t => {
fs.readFile('data.txt', t.end);
});
Optional TAP output
AVA can generate TAP output via --tap
option for use with any TAP reporter.
$ ava --tap | tap-nyan
Please note that the TAP reporter is unavailable when using watch mode.
Clean stack traces
AVA automatically removes unrelated lines in stack traces, allowing you to find the source of an error much faster.
Global timeout
A global timeout can be set via the --timeout
option.
Timeout in AVA behaves differently than in other test frameworks.
AVA resets a timer after each test, forcing tests to quit if no new test results were received within the specified timeout.
You can set timeouts in a human-readable way:
$ ava --timeout=10s # 10 seconds
$ ava --timeout=2m # 2 minutes
$ ava --timeout=100 # 100 milliseconds
API
test([title], implementation)
test.serial([title], implementation)
test.cb([title], implementation)
test.only([title], implementation)
test.skip([title], implementation)
test.todo(title)
test.failing([title], implementation)
test.before([title], implementation)
test.after([title], implementation)
test.beforeEach([title], implementation)
test.afterEach([title], implementation)
title
Type: string
Test title.
implementation(t)
Type: function
Should contain the actual test.
t
Type: object
The execution object of a particular test. Each test implementation receives a different object. Contains the assertions as well as .plan(count)
and .end()
methods. t.context
can contain shared state from beforeEach
hooks.
t.plan(count)
Plan how many assertion there are in the test. The test will fail if the actual assertion count doesn't match the number of planned assertions. See assertion planning.
t.end()
End the test. Only works with test.cb()
.
Assertions
Assertions are mixed into the execution object provided to each test implementation:
test(t => {
t.truthy('unicorn');
});
If multiple assertion failures are encountered within a single test, AVA will only display the first one.
.pass([message])
Passing assertion.
.fail([message])
Failing assertion.
.truthy(value, [message])
Assert that value
is truthy.
.falsy(value, [message])
Assert that value
is falsy.
.true(value, [message])
Assert that value
is true
.
.false(value, [message])
Assert that value
is false
.
.is(value, expected, [message])
Assert that value
is equal to expected
.
.not(value, expected, [message])
Assert that value
is not equal to expected
.
.deepEqual(value, expected, [message])
Assert that value
is deep equal to expected
.
.notDeepEqual(value, expected, [message])
Assert that value
is not deep equal to expected
.
.throws(function|promise, [error, [message]])
Assert that function
throws an error, or promise
rejects with an error.
error
can be a constructor, regex, error message or validation function.
Returns the error thrown by function
or the rejection reason of promise
.
.notThrows(function|promise, [message])
Assert that function
doesn't throw an error
or promise
resolves.
.regex(contents, regex, [message])
Assert that contents
matches regex
.
.ifError(error, [message])
Assert that error
is falsy.
Skipping assertions
Any assertion can be skipped using the skip
modifier. Skipped assertions are still counted, so there is no need to change your planned assertion count.
test(t => {
t.plan(2);
t.skip.is(foo(), 5);
t.is(1, 1);
});
Enhanced assertion messages
AVA comes with power-assert
built-in, giving you more descriptive assertion messages. It reads your test and tries to infer more information from the code.
Let's take this example, using Node's standard assert
library:
const a = /foo/;
const b = 'bar';
const c = 'baz';
require('assert').ok(a.test(b) || b === c);
If you paste that into a Node REPL it'll return:
AssertionError: false == true
In AVA however, this test:
test(t => {
const a = /foo/;
const b = 'bar';
const c = 'baz';
t.true(a.test(b) || b === c);
});
Will output:
t.true(a.test(b) || b === c)
| | | |
| "bar" "bar" "baz"
false
Process isolation
Each test file is run in a separate Node.js process. This allows you to change the global state or overriding a built-in in one test file, without affecting another. It's also great for performance on modern multi-core processors, allowing multiple test files to execute in parallel.
Tips
Temp files
Running tests concurrently comes with some challenges, doing file IO is one.
Usually, serial tests create temp directories in the current test directory and clean them up at the end. This won't work when you run tests concurrently as tests will conflict with each other. The correct way to do it is to use a new temp directory for each test. The tempfile
and temp-write
modules can be helpful.
Debugging
AVA runs tests concurrently by default, which is suboptimal when you need to debug something. Instead, run tests serially with the --serial
option:
$ ava --serial
Code coverage
You can't use istanbul
for code coverage as AVA spawns the test files. You can use nyc
instead, which is basically istanbul
with support for subprocesses.
As of version 5.0.0
it uses source maps to report coverage for your actual code, regardless of transpilation. Make sure that the code you're testing includes an inline source map or references a source map file. If you use babel-register
you can set the sourceMaps
option in your Babel config to inline
.
FAQ
Why not mocha
, tape
, tap
?
Mocha requires you to use implicit globals like describe
and it
with the default interface (which most people use). It's not very opinionated and executes tests serially without process isolation, making it slow.
Tape and tap are pretty good. AVA is highly inspired by their syntax. They too execute tests serially. Their default TAP output isn't very user-friendly though so you always end up using an external tap reporter.
In contrast AVA is highly opinionated and runs tests concurrently, with a separate process for each test file. Its default reporter is easy on the eyes and yet AVA still supports TAP output through a CLI flag.
How can I use custom reporters?
AVA supports the TAP format and thus is compatible with any TAP reporter. Use the --tap
flag to enable TAP output.
How is the name written and pronounced?
AVA, not Ava or ava. Pronounced /ˈeɪvə/
ay-və.
It's the Andromeda galaxy.
What is the difference between concurrency and parallelism?
Concurrency is not parallelism. It enables parallelism.
Recipes
Support
Related
More...
Links
Team
| | | |
---|---|---|---|---|---
Sindre Sorhus | Vadim Demedes | James Talmage | Mark Wubben | Juan Soto
Former