AVA Test Runner: Concurrent Tests and Minimal Configuration

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 ava

Configure 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/node

package.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-snapshots

Parallel 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 ava

When 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 testdouble or sinon)
  • 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.serial when tests in a file share state
  • t.context passes data between hooks and tests cleanly
  • t.plan() ensures you don't accidentally skip assertions
  • TypeScript works out of the box with minimal config

Read more