There are a lot of test frameworks for JavaScript/Node.js and I’ve been working with Mocha, Jasmine and Lab for a while. But I decided to give a try to AVA in a new project to be able to write faster specs in ES2017.
Different from other test frameworks, AVA runs tests in parallel by default. Not only by using Node asynchronous capabilities but by running every test in a separate process. This gives each test an isolated environment.
Main differences from other frameworks (I’ve used)
The table below shows the most significant features.
AVA doesn’t support BDD style by default, but Ava-Spec does (I’m using it)
Hapi/Lab can run tests in parallel, but I’ve never used it this way
Mocha is the only one that adds Implicit Globals, so describe and it are always available
Configuration
First of all, AVA configuration is done on package.json
"ava"
section. All client arguments can be set there.
My initial configuration looks like:
{
"ava": {
"require": [
"./test/support/bootstrap.js"
],
"files": [
"./test/**/*_spec.js"
]
}
}
require
: requires node modules prior to every test (I’ll explain the bootstrap.js below)files
: a glob pattern to tell which test files to run.
ES2017 support
It’s possible to write tests using ES2017 syntax with other frameworks too, since you can just require babel/register
or use node >= 7.0.0
. But AVA is the only one that comes with a babel compiler by default, so it’s quite easier and you’ll find more ES2017 examples for it.
The biggest advantage in using ES2017 is that it allows using async
and await
to deal with promises.
So, instead of writing:
it('creates a record in db', () => {
return new User().save()
.then((model) => expect(model).to.include.keys(['id']))
})
You can just write:
it('creates a record in db', async () => {
const model = await new User().save()
expect(model).to.include.keys(['id'])
})
This makes tests much more clear and easier to read.
Bootstrap / Initialize
I usually add a test/bootstrap.js
(or test/initialize.js
) file with a common setup for the tests. It includes loading env vars, initializing external services, setting common globals (as chai.expect
), cleaning up databases, etc.
For AVA there is a gotcha here; since this bootstrap file is required for every test, it’s not much ok to add heavy tasks to it. Mine just loads env vars.
process.env.NODE_ENV = 'test'
require('dotenv').config({ silent: true })
The goal of NOT adding heavy tasks to test/boostrap such as database cleanup, for example, is that smaller tests will run faster. For instance, when testing a simple lib that does not require a database connection, the test will run without connecting to db.
DB cleanup and dealing with concurrency
Since tests run in parallel, it’s not safe to rely on (global) stored data. A quick solution is to have one of:
several databases, one for each test
table/collection prefixes for each test
With Mongodb/Mongoose, it’s easy to have several databases, as they’re created on first write to a collection and the io is fast enough. So I’ve created a small helper to deal with connection and cleanup.
const {after, afterEach} = require('ava-spec')
/* use a different database for each test */
process.env.DATABASE_URL += -test-${process.pid}
/* initialize/connect to database */
const connection = require('../../../models/_connection')
const User = require('../../../models/user')
/* remove all documents */
const cleanDb = () => User.remove()
/* drop the database */
const dropDb = () => connection.db.dropDatabase()
/* defaults hooks for tests */
const dbHooks = () => {
afterEach(() => cleanDb())
after.always(() => dropDb())
}
module.exports = { cleanDb, dropDb, dbHooks }
So, on every spec that a database connection is required I just run:
const {dbHooks} = require('../support/helpers/database')
dbHooks()
This will create a new db
for each test using test and process.pid
as a suffix (e.g. mongodb://localhost:27017/database-test-999
), so that each test runs in a sandboxed database.
Caveats and Conclusions
AVA Is a full-featured test framework for JavaScript focused on speed. It achieves its goal by running tests in parallel in separate processes, which can use even more cores/processors to increase speed and isolate environments.
However, it’s not as easy or intuitive to setup as other JavaScript test frameworks and may be a little difficult for beginners, mostly due to concurrency handling. Also, the lack of a BDD style DSL forces you (if you prefer BDD, of course) to rely on extra libs.
I would recommend it for big projects where test suites can take several minutes, due to tests running in parallel and not depending on each other.
This post was first published on http://ift.tt/2p4kSId.
We want to work with you. Check out our "What We Do" section!