# A Beginner's Guide to Unit Testing with Japa

[Japa](https://japa.dev/) 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.

[ad-logs]

## Prerequisites

Before you begin, ensure that you have [Node.js](https://nodejs.org/en/download) 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:

```command
mkdir japa-demo && cd japa-demo
```

Next, set up a basic npm project:

```command
npm init -y
```

This creates a `package.json` file that tracks your project's details and dependencies.

Now install Japa and its testing tools:


```command
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:


```text
[output]

....

   ___                             _            
  |_  |                           | |           
    | | __ _ _ __   __ _        __| | _____   __
    | |/ _` | '_ \ / _` |      / _` |/ _ \ \ / /
/\__/ / (_| | |_) | (_| |  _  | (_| |  __/\ 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:

```json
[label package.json]
{
  . . .
  "scripts": {
    "test": "node bin/test.js"
  },
[highlight]
  "type": "module",
[/highlight]
   ...
}
```

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`:

```javascript
[label 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:

```command
mkdir -p tests
```

Now make a file called `calculator.spec.js` in the `tests` folder with this content:

```javascript
[label tests/calculator.spec.js]
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:

```command
npm test
```

This runs the test script you added to your `package.json` file. You should see output like this:

![Screenshot of the output](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/c875419e-1401-4f72-8e72-1644d1ca7d00/md2x =1404x1170)

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:

```javascript
[label tests/calculator.spec.js]
...
test.group('Calculator', () => {
  test('sum should add two numbers correctly', ({ assert }) => {
    assert.equal(sum(1, 2), 3)
    assert.equal(sum(5, 7), 12)
[highlight]
    assert.equal(sum(-1, 1), 1) // This is wrong - should be 0
[/highlight]
  })
```

Rerun the tests and you'll see:

![Screenshot of the failing test](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/f91d1456-d7f1-43f8-011f-102f089a7a00/lg2x =2048x1748)

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:

```javascript
[label tests/calculator.spec.js]
...
test.group('Calculator', () => {
  test('sum should add two numbers correctly', ({ assert }) => {
    assert.equal(sum(1, 2), 3)
    assert.equal(sum(5, 7), 12)
[highlight]
    assert.equal(sum(-1, 1), 0) // Fixed - now correct
[/highlight]
  })
```

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`:

```javascript
[label 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`:

```javascript
[label 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:

1. `group.each.setup()` - Creates a fresh counter before each test
2. `group.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:

```text
[output]
> 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 group
- `group.teardown()` - Runs once after all tests finish

These are perfect for things you only need to do once, like connecting to a database:

```javascript
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`:

```javascript
[label 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`:

```javascript
[label 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:

```text
[output]
...
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:

```javascript
[label tests/calculator.spec.js]
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)
[highlight]
  }).tags(['add', 'unit']);
[/highlight]

  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)
[highlight]
  }).tags(['substract', 'unit'])
[/highlight]
})
```

Now you can run only add-related tests with this command:

```command
npm test -- --tags=add
```

You should see only the calculator tests running:

```text
[output]
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:

```command
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:

```javascript
test.group('Calculator', () => {
[highlight]
   test.only('sum should add two numbers correctly', ({ assert }) => {
[/highlight]
  })

  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`:

```javascript
[highlight]
test.skip('subtract should subtract second number from first', ({ assert }) => {
[/highlight]
  // 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](https://japa.dev/).