AVA Test Runner: Concurrent Tests and Minimal Configuration
AVA is a Node.js test runner that runs tests concurrently by default. Each test file runs in its own process, which means no shared state between test files and faster feedback when you have many test files.
Installing AVA
npm install --save-dev avaConfigure in package.json:
{
"scripts": {
"test": "ava"
},
"ava": {
"files": ["tests/**/*.test.js"],
"timeout": "30s"
}
}Basic Tests
AVA tests are functions. The first argument t is the test object:
import test from 'ava';
test('adds numbers', t => {
t.is(1 + 1, 2);
});
test('returns an array', t => {
const result = getItems();
t.deepEqual(result, ['a', 'b', 'c']);
});Assertions
AVA has its own assertion library built in:
t.is(actual, expected) // strict equality (===)
t.not(actual, expected) // not strictly equal
t.deepEqual(actual, expected) // deep equality
t.notDeepEqual(actual, expected)
t.true(value) // strictly true
t.false(value) // strictly false
t.truthy(value) // truthy
t.falsy(value) // falsy
t.regex(string, /pattern/) // regex match
t.notRegex(string, /pattern/)
t.throws(fn, { instanceOf: Error })
t.throwsAsync(asyncFn, { message: /expected/ })
t.notThrows(fn)
t.notThrowsAsync(asyncFn)
t.pass() // force pass
t.fail('explicit failure message')Assertion count
AVA can verify you ran the expected number of assertions:
test('runs exactly 3 assertions', t => {
t.plan(3);
t.is(1 + 1, 2);
t.true(Array.isArray([]));
t.deepEqual({ a: 1 }, { a: 1 });
});Async Tests
Return a promise or use async/await:
test('fetches user', async t => {
const user = await getUser(1);
t.is(user.id, 1);
t.truthy(user.name);
});
test('rejects on invalid id', async t => {
await t.throwsAsync(() => getUser(-1), {
message: 'Invalid user ID'
});
});Concurrent vs Serial Tests
By default, test files run concurrently, but tests within a file run sequentially. To run tests within a file concurrently:
test.concurrent('fast test 1', async t => {
// runs in parallel with other concurrent tests
});
test.concurrent('fast test 2', async t => {
// runs in parallel
});To force a test to run serially (useful for tests that share resources):
test.serial('database setup', async t => {
await seedDatabase();
t.pass();
});
test.serial('reads seeded data', async t => {
const count = await db.count();
t.is(count, 10);
});Hooks
import test from 'ava';
test.before(async t => {
// Runs once before all tests in this file
t.context.db = await connectDB();
});
test.after.always(async t => {
// Runs even if tests fail
await t.context.db.close();
});
test.beforeEach(async t => {
await t.context.db.seed();
});
test.afterEach.always(async t => {
await t.context.db.truncate();
});
test('uses context', async t => {
const user = await t.context.db.findUser(1);
t.truthy(user);
});Skipping and Only
// Skip
test.skip('not ready', t => {
// ...
});
// Run only this test
test.only('focused', t => {
t.pass();
});TypeScript
AVA has first-class TypeScript support:
npm install --save-dev ts-node @types/nodepackage.json:
{
"ava": {
"extensions": {
"ts": "module"
},
"nodeArguments": ["--import=tsx/esm"]
}
}import test from 'ava';
interface User {
id: number;
name: string;
}
test('user has expected shape', t => {
const user: User = { id: 1, name: 'Alice' };
t.is(user.id, 1);
t.is(user.name, 'Alice');
});Snapshots
AVA supports snapshot testing:
test('renders correctly', t => {
const html = renderComponent({ title: 'Hello' });
t.snapshot(html);
});Update snapshots:
npx ava --update-snapshotsParallel File Execution in CI
AVA runs each test file in a worker thread. For CI with many test files, increase concurrency:
{
"ava": {
"workerThreads": true,
"concurrency": 5
}
}CI Configuration
# .github/workflows/test.yml
- name: Run AVA tests
run: npx ava --tap | npx tap-junit > test-results.xml
# Or with native spec reporter
- name: Run AVA tests
run: npx avaWhen to Choose AVA
AVA fits well when:
- You have many test files and want parallel execution
- You need process isolation between test files (no accidental global state)
- You want built-in assertions without an extra library
Consider alternatives when:
- You need module-level mocking (AVA doesn't mock modules natively; use
testdoubleorsinon) - You need browser environment testing (use Jest + jsdom or Vitest)
- You need snapshot UI testing
Key Takeaways
- Each test file runs in its own process — true isolation by default
- Use
test.serialwhen tests in a file share state t.contextpasses data between hooks and tests cleanlyt.plan()ensures you don't accidentally skip assertions- TypeScript works out of the box with minimal config