AVA is a fast, simple JavaScript test runner. It runs tests concurrently, supports modern JavaScript, and keeps testing simple with a clean, focused API.
AVA gives you isolated test environments, snapshot testing, and built-in assertions. This makes it great for testing both front-end and back-end JavaScript code.
This guide shows you how to use AVA's core features to write and run tests effectively.
Prerequisites
You need Node.js version 20.0 or higher.
You should also be familiar with JavaScript basics and understand basic testing concepts.
Step 1 — Setting up the project
Before you can write and run any tests with AVA, you need to set up a basic Node.js project. This step will guide you through creating a new folder, setting up your package.json
file, and installing all the necessary dependencies to get started with testing.
First, create a new directory and go into it:
mkdir ava-testing && cd ava-testing
Set up a new npm project:
npm init -y
This creates a package.json
file for your project's settings and dependencies.
Enable ES Modules support:
npm pkg set type="module"
Install AVA as a development dependency:
npm install --save-dev ava
Add a test script to your package.json
:
...
{
"scripts": {
"test": "ava"
}
}
AVA doesn't look for test files in any specific folder by default. Tell AVA where to find your tests by adding this to your package.json
:
{
"scripts": {
"test": "ava"
},
"ava": {
"files": [
"test/**/*.js"
]
}
...
}
Now let's create a simple function to test. Add this to a new file called utils.js
:
export function capitalize(string) {
if (typeof string !== 'string') return '';
return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
}
This function takes a string, capitalizes its first letter, and makes all other letters lowercase. Now let's write a test for it.
Step 2 — Writing your first test
Unit tests verify that your functions operate correctly in various scenarios. Instead of testing manually, AVA helps you automate this process.
Create a test folder:
mkdir test
Inside this folder, create a file called utils.test.js
:
import test from 'ava';
import { capitalize } from '../utils.js';
test('capitalize should transform the first character to uppercase', t => {
t.is(capitalize('hello'), 'Hello');
});
In this file, you:
- Import the
test
function from AVA - Import your
capitalize
function - Create a test with a clear description
- Use
t.is()
to check if the function returns the expected result
The t
object gives you methods to make assertions about your code.
Step 3 — Running your tests
Now run your test with this command:
npm test
This works because your package.json
file has' "test": "ava" '. It's the same as typing:
npx ava
AVA finds and runs your test files based on your package.json
settings. You'll see output like this:
This means your test ran and passed.
Unlike some testing tools, AVA does not automatically monitor file changes. To enable this, add a watch script to your package.json
:
{
"scripts": {
"test": "ava",
"test:watch": "ava --watch"
}
}
Now you can run tests in watch mode:
npm run test:watch
> ava-testing@1.0.0 test:watch
> ava --watch
✔ utils › capitalize should transform the first character to uppercase
─
1 test passed [09:52:57]
Type `r` and press enter to rerun tests
Type `u` and press enter to update snapshots
With watch mode enabled, AVA reruns your tests whenever you modify your code, providing instant feedback.
Step 4 — Writing multiple test cases
Let's make our tests more complete by checking different situations:
import test from 'ava';
import { capitalize } from '../utils.js';
test('capitalize should transform the first character to uppercase', t => {
t.is(capitalize('hello'), 'Hello');
});
test('capitalize should transform the rest of the string to lowercase', t => {
t.is(capitalize('HELLO'), 'Hello');
});
test('capitalize should handle empty strings', t => {
t.is(capitalize(''), '');
});
test('capitalize should handle non-string inputs', t => {
t.is(capitalize(null), '');
t.is(capitalize(undefined), '');
t.is(capitalize(123), '');
});
Each test focuses on a specific behavior of the capitalize
function. One test checks that the first letter of the string is correctly transformed to uppercase.
Another ensures that the rest of the string is converted to lowercase. There's also a test to verify how the function handles empty strings, and finally, a test confirms that non-string inputs like null
, undefined
, or numbers return an empty string.
Writing small, focused tests makes your test suite easier to maintain. When something breaks, you'll know exactly what failed.
Run all the tests:
npm test
You'll see all tests pass:
Step 5 — Running specific tests
As your test suite grows, you won’t always want to run every single test. Sometimes, you’ll just want to focus on a specific test while fixing a bug or trying out a new feature. AVA makes this easy with a couple of handy tools.
Using test.only
to focus on specific tests
If you’re working on a specific issue, you can use test.only()
to run just that one test. This helps you stay focused and avoid noise from unrelated tests.
Add the highlighted code below:
import test from 'ava';
import { capitalize } from '../utils.js';
test('capitalize should transform the first character to uppercase', t => {
t.is(capitalize('hello'), 'Hello');
});
test('capitalize should transform the rest of the string to lowercase', t => {
t.is(capitalize('HELLO'), 'Hello');
});
test.only('capitalize should handle empty strings', t => {
t.is(capitalize(''), '');
});
test('capitalize should handle non-string inputs', t => {
t.is(capitalize(null), '');
t.is(capitalize(undefined), '');
t.is(capitalize(123), '');
});
Now when you run tests, only the test with .only
runs:
> ava-testing@1.0.0 test
> ava
✔ capitalize should handle empty strings
─
1 test passed
This is extremely helpful when you need fast feedback while working on a single piece of logic.
Using test.skip
to exclude tests temporarily
Let’s say one of your tests is broken or not relevant right now, and you want to ignore it for the moment. You can use test.skip()
to instruct AVA to skip the test:
import test from 'ava';
import { capitalize } from '../utils.js';
test('capitalize should transform the first character to uppercase', t => {
t.is(capitalize('hello'), 'Hello');
});
test('capitalize should transform the rest of the string to lowercase', t => {
t.is(capitalize('HELLO'), 'Hello');
});
test('capitalize should handle empty strings', t => {
t.is(capitalize(''), '');
});
test.skip('capitalize should handle non-string inputs', t => {
t.is(capitalize(null), '');
t.is(capitalize(undefined), '');
t.is(capitalize(123), '');
});
Now that test will be skipped:
> ava-testing@1.0.0 test
> ava
- [skip] capitalize should handle non-string inputs
✔ capitalize should transform the first character to uppercase
✔ capitalize should transform the rest of the string to lowercase
✔ capitalize should handle empty strings
─
3 tests passed
1 test skipped
Skipping a test like this is excellent for keeping your test suite green while you work on fixes or updates.
Filtering tests with command line arguments
You can also run tests by matching their names. This is useful when you don't want to change your test files:
npm test -- --match="*lowercase*"
This command runs only tests with "lowercase" in their name:
✔ capitalize should transform the rest of the string to lowercase
─
1 test passed
The double dash (--
) separates npm's arguments from AVA's arguments. The stars (*
) match any text before or after "lowercase".
Step 6 — Testing asynchronous code
In modern applications, asynchronous code is everywhere, whether you're fetching data from an API, reading a file, or querying a database. AVA is built to handle async code smoothly, so you can write tests that wait for promises to resolve or reject without extra setup.
In this step, you'll create a simple async function and test it. Let's simulate a user data fetch with a fake delay.
In your project’s root directory, create a new file called users.js
and add the following code:
export async function fetchUserData(id) {
// Fake delay like a real API call
await new Promise(resolve => setTimeout(resolve, 100));
if (!id) {
throw new Error('User ID is required');
}
// Return fake user data
return {
id,
name: `User ${id}`,
email: `user${id}@example.com`
};
}
This function mimics a typical async operation: it waits for a short delay and either returns fake user data or throws an error if no ID is provided.
Now let’s test this function. Create a new file called users.test.js
inside your test
folder and add the following:
import test from 'ava';
import { fetchUserData } from '../users.js';
test('fetchUserData returns user data for valid ID', async t => {
const user = await fetchUserData(1);
t.is(user.id, 1);
t.is(user.name, 'User 1');
t.is(user.email, 'user1@example.com');
});
test('fetchUserData throws error for missing ID', async t => {
await t.throwsAsync(async () => {
await fetchUserData();
}, { message: 'User ID is required' });
});
The first test waits for fetchUserData(1)
to return and then checks if the data is correct. The second test uses t.throwsAsync()
to make sure the function throws an error when no ID is provided. Since AVA understands how to handle promises, it automatically waits for async functions to finish before running assertions.
If you only want to run the tests from this file, you can use one of the following command:
npm test -- test/users.test.js
If everything works, your test output will look something like this:
✔ fetchUserData returns user data for valid ID (103ms)
✔ fetchUserData throws error for missing ID (102ms)
─
2 tests passed
You're now successfully testing asynchronous code. In the next section, you'll learn how to mock your code using AVA.
Step 7 — Mocking with AVA
When testing your code, it's often a good idea to mock external dependencies, such as APIs, databases, or file systems. Mocking means replacing those real components with fake ones so you can test your code’s behavior in isolation.
AVA doesn’t include mocking out of the box, but it works smoothly with libraries like Sinon.js.
npm install --save-dev sinon
Let’s say you have a service that uses an API client to fetch a user’s name:
export class UserService {
constructor(client) {
this.client = client;
}
async getUserName(id) {
try {
const user = await this.client.fetchUser(id);
return user.name;
} catch (error) {
throw new Error(`Failed to get user: ${error.message}`);
}
}
}
This method fetches a user and returns their name. If something goes wrong, it throws a custom error.
Now create a test file at test/userService.test.js
:
import test from 'ava';
import sinon from 'sinon';
import { UserService } from '../userService.js';
test('getUserName returns user name for valid ID', async t => {
// Create a fake client
const mockClient = {
fetchUser: sinon.stub().resolves({ id: 1, name: 'John Doe' })
};
// Use the fake client
const userService = new UserService(mockClient);
// Test the method
const name = await userService.getUserName(1);
// Check the result
t.is(name, 'John Doe');
// Verify the client was called correctly
t.true(mockClient.fetchUser.calledOnceWith(1));
});
test('getUserName handles errors properly', async t => {
// Create a fake client that causes an error
const mockClient = {
fetchUser: sinon.stub().rejects(new Error('Network error'))
};
// Use the fake client
const userService = new UserService(mockClient);
// Check that we get the right error
const error = await t.throwsAsync(async () => {
await userService.getUserName(1);
});
t.is(error.message, 'Failed to get user: Network error');
});
These tests simulate both success and error cases using mocked behavior. You’re not calling any real APIs; instead, you're controlling the behavior of the fetchUser
method using sinon.stub()
.
Now run the userService.test.js
file:
npm test -- test/userService.test.js
Both commands will run only the tests in this file. This is super helpful when you're working on one specific part of your app.
If all goes well, you should see something like:
✔ getUserName returns user name for valid ID
✔ getUserName handles errors properly
─
2 tests passed
With this, you've learned how to mock dependencies in AVA and isolate your logic for accurate, reliable testing.
Final thoughts
This guide walked you through AVA’s key features, including setting up a project, writing tests, handling async code, and using mocks. AVA is simple yet powerful.
For more advanced use, visit the official docs. Most importantly, testing helps you build confidence in your code so you can make changes without worry.
Make your mark
Join the writer's program
Are you a developer and love writing and sharing your knowledge with the world? Join our guest writing program and get paid for writing amazing technical guides. We'll get them to the right readers that will appreciate them.
Write for us
Build on top of Better Stack
Write a script, app or project on top of Better Stack and share it with the world. Make a public repository and share it with us at our email.
community@betterstack.comor submit a pull request and help us build better products for everyone.
See the full list of amazing projects on github