AVA: Futuristic JavaScript Test Runner

AVA

Why AVA?

  • Simple test syntax like tape (e.g. t.deepEqual, t.pass, …)
  • No implicit globals like mocha (e.g describe , it)
  • Runs tests concurrently (troll!)
  • Process isolation for each test file
  • Support ES2017 by Babel (stage-4)
  • Support:
    • Promise
    • Generator
    • async/await
    • Observable

Basic usage

  • Take a look
1
2
3
4
import test from 'ava';
test('just a demo', t => {
t.deepEqual([1, 2], [1, 2]);
});
  • Run
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# install ava cli
$ npm install -g ava
# install dependency & run
$ npm install ava
# run
$ ava
# auto reload
$ ava --watch
# Or you can add the testing script for package.json.
$ ava --init
$ npm test
  • Simple result

Simple result

  • Verbose result

Verbose result

  • Tap style result
    • Can integrate with any tap reporters.

Tap sytle result

Tap style result

Enhanced assertion messages

  • Fail case
1
2
3
4
5
6
7
8
9
10
11
12
import test from 'ava';
test('enhanced assertion messages - case 1', t => {
const a = {a: 10, b: 20};
const b = {a: 20, b: 20}
t.deepEqual(a, b);
});
test('enhanced assertion messages - case 2', t => {
const a = 'bar';
const b = 'bar';
const c = 'baz';
t.true((a === b) && (b === c));
});
  • Result
    • case 1

enhanced assertion messages - case 1

  • case 2

enhanced assertion messages - case 2

  • You can use any assertion library (e.g. chai, node assert).
  • But the magic assert only supports with default assertion library.

Babel

  • Write babel presets in ava field of package.json.
1
2
3
4
5
"ava": {
"babel": {
"presets": ["es2015", "stage-0"]
}
}
  • Or just inherit from .babelrc.
1
2
3
"ava": {
"babel": "inherit"
}
  • If you need to load the extra babel modules, use require option in the babel field.
1
2
3
4
5
6
7
"ava": {
"babel": "inherit",
"require": [
"babel-register",
"babel-polyfill"
]
}
  • Or you want to load from an entrypoint.
1
2
3
4
5
6
"ava": {
"babel": "inherit",
"require": [
"./test/set-babel.js"
]
}
  • ./test/set-babel.js
1
2
require('babel-register')
require('babel-polyfill')

Test coverage

  • Cannot use istanbul for code coverage. Use nyc (Istanbul CLI) instead.
1
$ nyc ava

test coverage

Promise

1
2
3
4
5
test(t => {
return somePromise().then(result => {
t.is(result, 'unicorn');
});
});

Generator

1
2
3
4
test(function* (t) {
const value = yield generatorFn();
t.true(value);
});

async/await

1
2
3
4
5
6
7
8
9
10
test(async function (t) {
const value = await promiseFn();
t.true(value);
});
// Async arrow function
test(async t => {
const value = await promiseFn();
t.true(value);
});

Observable

1
2
3
4
5
6
7
8
9
test(t => {
t.plan(3);
return Observable.of(1, 2, 3, 4, 5, 6)
.filter(n => {
// Only even numbers
return n % 2 === 0;
})
.map(() => t.pass());
});

Callback

  • use test.cb()
  • t.end() only work with test.cb() , must be called at the end of callback.
1
2
3
4
test.cb(t => {
// `t.end` automatically checks for error as first argument
fs.readFile('data.txt', t.end);
});

Assertions

  • .pass([message]) / .fail([message])
  • .truthy(value, [message]) / .falsy(value, [message])
  • .true(value, [message]) / .false(value, [message])
  • .is(value, expected, [message]) / .not(value, expected, [message])
  • .deepEqual(value, expected, [message]) / .notDeepEqual(value, expected, [message])
  • .throws(function|promise, [error, [message]]) / .notThrows(function|promise, [message])
  • .regex(contents, regex, [message]) / .notRegex(contents, regex, [message])
  • .ifError(error, [message])
  • .snapshot(contents, [message])

Snapshot

  • AVA can take a snapshot which uses jest-snapshot under the hood.

Example:

  • hello.jsx
1
2
3
4
import React from 'react'
import ReactDOM from 'react-dom'
const Hello = () => <h1>Hello</h1>
export default Hello
  • react.spec.js
1
2
3
4
5
6
7
8
9
import test from 'ava'
import React from 'react'
import render from 'react-test-renderer'
import Hello from '../app/hello.jsx'
test('test react component', async t => {
const dom_tree = render.create(<Hello />).toJSON()
console.log(dom_tree)
t.snapshot(dom_tree)
})
  • It will create a __snapshots__ folder in the test folder and contains the following result.
    • First run

First run

  • Snapshot result

A snapshot in the __snapshots__ folder

  • And when the result is not equal to the snapshot, it will fail the test.

The result was changed

before/after Hooks

  • test.before(): Hooks before the first test.
1
2
3
4
5
const db = init_db(db_config)
test.before(async t => {
// db connection
await db.sequelize.sync({ force: true })
})
  • test.after(): Hooks after the last test.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
test.after.always(async t => {
// close the db connection
await db.sequelize.close()
})
```
- If you want to guarantee to run the hook (even failure), use `test.after.always()`
## beforeEach/afterEach Hooks
- `test.beforeEach()`: Hooks before each test.
```javascript
test.beforeEach(async t => {
t.context.clients = {}
})
```
- `test.afterEach()`: Hooks after each test.
- Same as `test.after()`, use `test.after.always()` to guarantee the hook.
```javascript
test.afterEach.always(async t => {
Object.keys(t.context.clients)
.map(client => t.context.clients[client].disconnect())
})
  • You can share the context (t.context) in each test. (Only works for beforeEach/afterEach hooks)
1
2
3
test(async t => {
t.context.clients['client1'] = socket.connect(socket_url, client_ops)
})

Other APIs

  • test.serial(): Force test serialization.
  • test.only(): Running specific tests (effect all test files).
  • test.skip(): Just skip the test.
  • test.todo(): Add a todo test, AVA will ignore it.
  • test.failing(): Mark known failure.

Without .failing()

With .failing()

Mocking

Debug in Chrome DevTools

  • Launch test file by inspect-process.
  • Just use debugger keyword in the test file.
1
2
$ npm install --global inspect-process
$ inspect node_modules/ava/profile.js <your-test-file>

Debug in VS Code

  • Add following configuration in the launch.json
  • Set the breakpoints and run.
1
2
3
4
5
6
7
8
9
10
11
"configurations": [
{
"type": "node",
"request": "launch",
"name": "ava test",
"program": "${workspaceRoot}/node_modules/ava/profile.js",
"args": [
"${file}"
]
}
]

Trolls & Bad things

  • Must run with ava-cli
  • Only can limit the execution time by CLI (global setting)
    • ava –timeout=30s
    • Cannot set for each test case.
  • Must write tests carefully because of the concurrency.
    • e.g. Test transactions in a same table.
    • Force test serialization
      • test.serial()
      • $ ava --serial

Other reference

Share