Japa is a fast and straightforward testing tool built for Node.js applications. It helps you write and run tests easily, while also allowing you to add more features as needed.
What makes Japa great is its clean design, straightforward way of checking test results, and plugin system that lets you add exactly what you need. It works well for testing Node.js apps of any size.
In this guide, you'll learn how to use Japa's main features and write good tests with it.
Prerequisites
Before you begin, ensure that you have Node.js installed on your computer. Version 20.0 or newer works best.
Step 1 — Setting up the project
Let’s start by creating a project directory and setting up Japa so you can begin testing your JavaScript code.
First, make a new folder and go into it:
mkdir japa-demo && cd japa-demo
Next, set up a basic npm project:
npm init -y
This creates a package.json
file that tracks your project's details and dependencies.
Now install Japa and its testing tools:
npm init japa@latest .
Choose the following options:
- project type: javascript
- assertion library:
@japa/assert
- additional plugins: none
- create sample test: false
You’ll see output like this:
....
___ _
|_ | | |
| | __ _ _ __ __ _ __| | _____ __
| |/ _` | '_ \ / _` | / _` |/ _ \ \ / /
/\__/ / (_| | |_) | (_| | _ | (_| | __/\ V /
\____/ \__,_| .__/ \__,_| (_) \__,_|\___| \_/
| |
|_|
❯ Select the project type · javascript
❯ Select the assertion library · @japa/assert
❯ Select additional plugins · No items were selected
❯ Want us to create a sample test? (y/N) · false
DONE: create bin/test.js
DONE: update package.json
added 90 packages, and audited 91 packages in 5s
27 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
╭──────────────────────────────────╮
│ Japa setup complete 🧪 │
│──────────────────────────────────│
│ │
│ > npm run test │
│ │
╰──────────────────────────────────╯
Japa creates a bin/test.js
file and updates your package.json
, so you're ready to start writing tests right away.
Set up ES modules in your package.json
file:
{
. . .
"scripts": {
"test": "node bin/test.js"
},
"type": "module",
...
}
We added "type": "module"
so you can use modern JavaScript import/export
syntax.
Let's create a simple calculator function to test. Make a file called src/calculator.js
:
export function sum(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
This file has two basic functions: sum
adds numbers, and subtract
finds the difference between them. Now you're ready to write your first test.
Step 2 — Writing your first test
Unit tests verify that your functions operate correctly in various scenarios. Japa helps you write clear tests that show how your code should work.
Create a tests
folder in your project:
mkdir -p tests
Now make a file called calculator.spec.js
in the tests
folder with this content:
import { test } from '@japa/runner'
import { sum, subtract } from '../src/calculator.js'
test.group('Calculator', () => {
test('sum should add two numbers correctly', ({ assert }) => {
assert.equal(sum(1, 2), 3)
assert.equal(sum(5, 7), 12)
assert.equal(sum(-1, 1), 0)
})
test('subtract should subtract second number from first', ({ assert }) => {
assert.equal(subtract(5, 2), 3)
assert.equal(subtract(10, 5), 5)
assert.equal(subtract(1, 5), -4)
})
})
Here's what this test does:
test.group
bundles related tests together under the name "Calculator"- Each
test
function describes what specific behavior you expect - The
assert.equal
checks if your functions give the right answers for different inputs
This approach makes your tests easy to understand and shows exactly what your code should do.
Now let's run our tests to see them in action.
Step 3 — Running your tests
Now that you've written your test, let's run it.
Type this command in your terminal:
npm test
This runs the test script you added to your package.json
file. You should see output like this:
Japa displays a clean report that makes it easy to see which tests ran and whether they passed. The checkmarks (✓) show successful tests.
Let's see what happens when a test fails. Change one line in your test file:
...
test.group('Calculator', () => {
test('sum should add two numbers correctly', ({ assert }) => {
assert.equal(sum(1, 2), 3)
assert.equal(sum(5, 7), 12)
assert.equal(sum(-1, 1), 1) // This is wrong - should be 0
})
Rerun the tests and you'll see:
Japa clearly shows which test failed and why, it expected 1 but got 0. This helps you quickly identify and resolve issues.
Fix the test by changing the line back:
...
test.group('Calculator', () => {
test('sum should add two numbers correctly', ({ assert }) => {
assert.equal(sum(1, 2), 3)
assert.equal(sum(5, 7), 12)
assert.equal(sum(-1, 1), 0) // Fixed - now correct
})
Now you know how to run tests and understand the results. Let's look at some more advanced Japa features.
Step 4 — Test setup and cleanup
When writing tests, you often need to prepare things before each test and clean up afterward. Japa has special functions called "lifecycle hooks" that help with this.
Let's create a simple counter to test these hooks. Make a file named src/counter.js
:
export class Counter {
constructor(initialValue = 0) {
this.count = initialValue;
}
increment() { return ++this.count; }
decrement() { return --this.count; }
getValue() { return this.count; }
}
This counter keeps track of a number that you can increase, decrease, or reset.
Now create a test file at tests/counter.spec.js
:
import { test } from '@japa/runner'
import { Counter } from '../src/counter.js'
test.group('Counter', (group) => {
let counter
// Runs before each test
group.each.setup(() => {
counter = new Counter()
})
test('should start with zero', ({ assert }) => {
assert.equal(counter.getValue(), 0)
})
test('should increment count', ({ assert }) => {
counter.increment()
assert.equal(counter.getValue(), 1)
})
})
This test file uses two important hooks:
group.each.setup()
- Creates a fresh counter before each testgroup.each.teardown()
- Cleans up after each test
These hooks ensure each test starts with a clean counter. This keeps tests independent so they don't affect each other - a key part of good testing.
Run the tests with npm test
and you'll see they all pass:
> japa-demo@1.0.0 test
> node bin/test.js
Calculator (tests/calculator.spec.js)
✔ sum should add two numbers correctly (0.62ms)
✔ subtract should subtract second number from first (0.08ms)
Counter (tests/counter.spec.js)
✔ should start with zero (0.07ms)
✔ should increment count (0.05ms)
PASSED
Tests 4 passed (4)
Time 3ms 6 tests in 15ms
Japa also has hooks that run just once for all tests in a group:
group.setup()
- Runs once before any tests in the groupgroup.teardown()
- Runs once after all tests finish
These are perfect for things you only need to do once, like connecting to a database:
test.group('Database operations', (group) => {
let db
// Connect once before all tests
group.setup(async () => {
db = await connectToDatabase()
})
// Disconnect after all tests finish
group.teardown(async () => {
await db.close()
})
// Your tests here...
})
Using these hooks makes your tests cleaner, faster, and more reliable.
Step 5 — Testing async code
Modern applications often rely on asynchronous operations, such as API requests, file reading, or database access. With built-in support for Promises and async/await
, Japa makes it easy to write tests for this kind of code.
Let's create a service that pretends to fetch user data from an API. Make a file called src/user-service.js
:
export class UserService {
async fetchUser(id) {
// Fake a network delay
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 service has an async fetchUser
method that returns user data after a short delay.
Now create a test file at tests/user-service.spec.js
:
import { test } from "@japa/runner";
import { UserService } from "../src/user-service.js";
test.group("UserService", (group) => {
let userService;
group.each.setup(() => {
userService = new UserService();
});
test("should fetch user by id", async ({ assert }) => {
const user = await userService.fetchUser(1);
assert.equal(user.id, 1);
assert.equal(user.name, "User 1");
assert.equal(user.email, "user1@example.com");
});
test("should throw error when id is not provided", async ({ assert }) => {
await assert.rejects(async () => {
await userService.fetchUser();
}, "User ID is required");
});
});
There are two key things to notice in this example. First, the test functions are marked as async
, which allows you to use await
inside them.
Second, the test uses assert.rejects
to verify that the function throws the correct error. When testing asynchronous code in Japa, you have two options: either return the Promise from your test function, or use async
and await
as shown here.
Japa waits for your async operations to finish before deciding if a test passes or fails.
Run the tests and you'll see they all pass:
...
UserService (tests/user-service.spec.js)
✔ should fetch user by id (101.08ms)
✔ should throw error when id is not provided (101.57ms)
PASSED
Tests 6 passed (6)
Time 209ms
Notice that the total time is longer (209ms) due to the delay in our fake API calls. Japa correctly waits for these delays before showing the results.
Step 6 — Filtering and organizing tests
As your test suite grows, you'll want to run specific tests rather than the entire suite. Japa offers several ways to filter and organize your tests.
Let's add tags to our calculator tests so we can run them selectively:
import { test } from '@japa/runner'
import { sum, subtract } from '../src/calculator.js'
test.group('Calculator', () => {
test('sum should add two numbers correctly', ({ assert }) => {
assert.equal(sum(1, 2), 3)
assert.equal(sum(5, 7), 12)
assert.equal(sum(-1, 1), 0)
}).tags(['add', 'unit']);
test('subtract should subtract second number from first', ({ assert }) => {
assert.equal(subtract(5, 2), 3)
assert.equal(subtract(10, 5), 5)
assert.equal(subtract(1, 5), -4)
}).tags(['substract', 'unit'])
})
Now you can run only add-related tests with this command:
npm test -- --tags=add
You should see only the calculator tests running:
Calculator (tests/calculator.spec.js)
✔ sum should add two numbers correctly (0.61ms)
PASSED
Tests 1 passed (1)
Time 3ms
You can also run tests from specific groups using the --groups
flag:
npm test -- --groups=Calculator
Focusing on specific tests
When debugging, you might want to focus on just one test temporarily. Japa provides .only
and .skip
modifiers for this purpose:
test.group('Calculator', () => {
test.only('sum should add two numbers correctly', ({ assert }) => {
})
test('subtract should subtract second number from first', ({ assert }) => {
// This test will be skipped when using .only above
})
})
With this change, Japa will run only the test marked with .only
, ignoring all other tests.
Similarly, you can skip specific tests with .skip
:
test.skip('subtract should subtract second number from first', ({ assert }) => {
// This test will be skipped
})
Remember to remove .only
and .skip
modifiers before committing your code, as they're meant for temporary use during development.
Final thoughts
Japa is a simple yet powerful testing tool for Node.js. In this guide, you learned how to set up a project, write tests, handle async code, use hooks, and filter tests with tags.
Its clean design and flexible features make it great for both small scripts and large applications. As your project grows, Japa scales with you. For more, check out the official docs.
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