Grouping tests

Grouping tests

You may group a collection of tests using the test.group method.

The test.group method accepts the group title as the first parameter and a callback function as the second parameter. The callback function receives an instance of the TestGroup class.

import { test } from '@japa/runner'
test.group('Maths.add', (group) => {
test('add two numbers', ({ assert }) => {
assert.equal(2 + 2, 4)
})
})

You may use the group object to bulk configure tests that are part of the surrounded group. In the following example, we define the timeout, after which a test will be marked as failed if not completed.

import { test } from '@japa/runner'
test.group('register user', (group) => {
group.each.timeout(1000 * 60)
test('create a new user', async ({ assert }) => {
})
})

Defining lifecycle hooks

You may define lifecycle hooks for individual or all the tests using the group object.

In the following example, we define a lifecycle hook using the each.setup method to create database tables before every test and remove them afterward.

See also: Lifecycle hooks

import { test } from '@japa/runner'
test.group('register user', (group) => {
group.each.setup(async () => {
await createTables()
return () => dropTables()
})
test('create a new user', async ({ assert }) => {
})
})

Tapping into tests

You may use the test.tap method to access the underlying test object for each test defined within the group.

This method opens possibilities for bulk/conditionally configuring tests. For example, prefixing the test titles with the it keyword.

import { test } from '@japa/runner'
test.group('polls list', (group) => {
group.tap((t) => {
t.options.title = `it ${t.options.title}`
})
test('shows list of public polls', () => {
})
test('shows list of participating polls', () => {
})
test('shows list of authored polls', () => {
})
})
node bin/test.js
Output
functional / polls list (tests/functional/polls/list/logged_in.spec.js)
✔ it shows list of public polls (0.53ms)
✔ it shows list of participating polls (0.04ms)
✔ it shows list of authored polls (0.04ms)

Can I write nested groups?

Japa does not allow the creating nested groups. We made this opinionated design choice when creating Japa, and let us share that with you.

Use folders over groups for organization

A common use case for nested groups is organization of tests. For example:

  • You might want to create a top-level group for users tests.
    • A nested group to list all users.
    • A nested group to create a new user
      • Another level of nesting to create a new user as admin
      • A sibling group to create a new user via registration form

The same level of organization can be achieved through nested files and folders. In fact, creating nested folders offers better filtering capabilities and makes it easy to visualize the depth of your tests by looking at the folder structure.

Prefer duplication over wrong abstractions

Another use-case for nested groups is to use layers of lifecycle hooks and avoid duplication at all costs. For example, creating some state in a top-level group so that you do not have to redefine it in every group.

let user
test.group('A top level group', (group) => {
group.setup(() => {
user = await getUserForTesting()
})
test.group('a nested group', () => {
test.group('another level of testing', () => {
test('use the user in the test', () => {
console.log(user)
})
})
})
})

While writing the code to get the user in just one place sounds good in theory, it can be problematic when you have long test files and want to track every usage of the user variable to ensure that you are not overwriting it somewhere in between.

Kent C. Dodds has written a great article titled "Avoid Nesting when you're Testing", touching on the topic of nested test groups. We recommend reading it through.