Logo Vincent
Back to all posts

A Lightweight JS Testing Framework: AVA

Web
A Lightweight JS Testing Framework: AVA
Table of Contents

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 vincentqiao.com . All rights reserved.