Logo Vincent
Back to all posts

A Lightweight JS Testing Framework: AVA

Web
A Lightweight JS Testing Framework: AVA

Preface

Common JS testing frameworks include Jest, Mocha, etc.

Today we’ll introduce a lightweight JS testing framework: AVA.

https://github.com/avajs/ava

Installation

Installation is straightforward:

npm i -D ava

After installation, add an npm script:

{
	"name": "awesome-package",
	"type": "module",
	"scripts": {
		"test": "ava"
	},
	"devDependencies": {
		"ava": "^5.0.0"
	}
}

Configuration File

AVA supports adding configuration in package.json,

or using an ava.config.js file:

module.exports = {
  files: ['!__tests__/**/*'],
  match: [],
  concurrency: 5,
  failFast: true,
  failWithoutAssertions: false,
  environmentVariables: {
    MY_ENVIRONMENT_VARIABLE: 'some value',
  },
  verbose: true,
  require: [],
  nodeArguments: ['--trace-deprecation'],
};

For detailed configuration usage, see:

https://github.com/avajs/ava/blob/main/docs/06-configuration.md

Test Cases

AVA supports common test cases.

Basic Test Case

// ava
const test = require('ava');

// test 1
test('test 1', (t) => {
  t.pass();
});

Running Multiple Test Cases in Parallel

// ava
const test = require('ava');

// test 1
test('test 1', (t) => {
  t.pass();
});

// test 2
test('test 2', (t) => {
  t.pass();
});

Running Multiple Test Cases Serially

// ava
const test = require('ava');

// test 1
test('test 1', (t) => {
  t.pass();
});

// test 1-1
test.serial('test 1-1', (t) => {
  t.pass();
});

// test 2
test('test 2', (t) => {
  t.pass();
});

Promise Test Cases

// ava
const test = require('ava');

// q
const { isExists } = require('../../index.js');

// test promise
test('test promise', (t) => {
  const fpath = '/path/not/exists';
  return isExists(fpath).then((result) => {
    t.falsy(result);
  });
});

Async / Await Test Cases

// ava
const test = require('ava');

// q
const { isExists } = require('../../index.js');

// test promise
test('test promise', async (t) => {
  const fpath = '/path/not/exists';
  const res = await isExists(fpath);
  t.falsy(res);
});

Running Only a Specific Test Case

// ava
const test = require('ava');

// test 1
test('test 1', (t) => {
  t.pass();
});

// test 2
test.only('test 2', (t) => {
  t.pass();
});

Skipping a Test Case

// ava
const test = require('ava');

// test 1
test('test 1', (t) => {
  t.pass();
});

// test 2
test('test 2', (t) => {
  t.pass();
});

// test 3
test.skip('test 3', (t) => {
  t.fail();
});

Todo Test Cases

// ava
const test = require('ava');

// test 1
test('test 1', (t) => {
  t.pass();
});

// test 2
test('test 2', (t) => {
  t.pass();
});

// test 3
test.skip('test 3', (t) => {
  t.fail();
});

// test 4
test.todo('test 4');

Expected-Failing Test Cases

// ava
const test = require('ava');

// test 1
test('test 1', (t) => {
  t.pass();
});

// test 2
test('test 2', (t) => {
  t.pass();
});

// test 3
test.skip('test 3', (t) => {
  t.fail();
});

// test 4
test.todo('test 4');

// test 5
test.failing('test 5', (t) => {
  t.fail(); // Test will count as passed
});

Hooks: before, after, etc.

The before hook runs before all test cases:

// ava
const test = require('ava');

// before
test.before(() => {
  console.log('before 1');
});

// test
test('test 1', (t) => {
  t.pass();
});
test('test 2', (t) => {
  t.pass();
});
test.serial('test serial', (t) => {
  t.pass();
});

The serial before hook runs serially before all test cases:

// ava
const test = require('ava');

// serial before
test.serial.before(() => {
  console.log('before serial 1');
});

// test
test('test 1', (t) => {
  t.pass();
});
test('test 2', (t) => {
  t.pass();
});
test.serial('test serial', (t) => {
  t.pass();
});

The after and after.always hooks run after all test cases:

// ava
const test = require('ava');

// after
test.after(() => {
  console.log('after');
});
test.after.always(() => {
  console.log('after always');
});

// test
test('test 1', (t) => {
  t.pass();
});
test('test 2', (t) => {
  t.pass();
});
test.serial('test serial', (t) => {
  t.pass();
});

Test Context

// ava
const test = require('ava');

test.beforeEach((t) => {
  t.context.data = 'foo';
});

test('context data is foo', (t) => {
  t.is(t.context.data + 'bar', 'foobar');
});

Macros

// ava
const test = require('ava');

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);

Execution Context (t)

The t parameter in the test callback is the execution context:

  • title: the test case title
  • context: the test case context
  • passed: whether the test case passed
  • log method: prints logs, replacing console.log
// ava
const test = require('ava');

// test
test('test', (t) => {
  t.log(t.title);
  t.log(t.context);
  t.log(t.passed);
  t.pass();
});

Assertions

AVA supports common assertions:

Setting Timeouts

// ava
const test = require('ava');

// test
test('test', async (t) => {
  t.timeout(1);

  await hello();
  t.pass();
});

function hello() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, 1000);
  });
}

Summary

AVA is lightweight but very convenient to use:

  1. Powerful configuration file
  2. Powerful test cases
  3. Powerful hooks and macros
  4. Powerful assertions
  5. Powerful CLI

Feel free to explore further:

https://github.com/avajs/ava/tree/main/docs

© 2026 Vincent. All rights reserved.