Organise your tests with describe() blocks

Often you will have lots of tests within a file. Organising them is normally done by breaking them up into describe() blocks.

This improves readability and allows you to group related tests together logically.

There are lots of approaches on how to split up your test files with describe blocks.

Some common ways include:

  • If you are testing a class, then each public method would have its own describe() block
  • If you are testing standalone functions, they might have their own describe block
  • You might test the happy path (successful function calls) in its own describe() block, and the unhappy path (such as when errors are thrown) in another.
  • Testing by user roles or permissions (e.g. a describe block for admin users, a describe block for unauthenticated users)
  • Testing by feature or use case
  • Testing by HTTP endpoint or HTTP method (GET, POST, etc)

At the end of the day it doesn't matter how you organise them - as long as you and your team know where tests should belong.

A describe() block is a function that takes two arguments:

  • the name (string)
  • a function that contains other describe() blocks or the actual tests.

The same applies for both Vitest and Jest.

Example of using describe block to test a class

Here is a made-up example where we have a class (referenced as service), and we are splitting up the tests for each public method:

  • getBlogPosts()
  • createBlogPost()

Within these describe blocks we can have multiple tests (and each test() function itself can have multiple assertions)

// Test assumes `service` is some class that needs testing
// for simplicity, its implementation is not shown here.
describe('getBlogPosts()', () => {
  test('it gets blog posts', () => {
    expect(
      service.getBlogPosts()
    ).toHaveLength(1);
  });

  test('gets archived posts', () => {
    expect(
      service.getBlogPosts({
        archived: true,
      })
    ).toHaveLength(2);
  });
});

describe('createBlogPost()', () => {
  test('it creates a blog post', async () => {
    expect(
      await service.createBlogPost({
        title: 'Hello',
      })
    ).toBe(true);
  });
});

Nested describe blocks

You can nest your describe() blocks. It is a very common pattern to have a single outer describe block (although really it often doesn't add much value).

describe('BlogService', () => {
  describe('getBlogPosts()', () => {
    describe('when posts exist', () => {
      test('it gets blog posts', () => {
        expect(
          service.getBlogPosts()
        ).toHaveLength(1);
      });
    });

    describe('when no posts exist', () => {
      test('returns empty array', () => {
        expect(
          service.getBlogPosts()
        ).toHaveLength(0);
      });
    });
  });
});

Setup and teardown

We will have a future lesson on setup and teardown functions, but just in case you wanted to see the syntax now, you can use beforeAll, beforeEach, afterEach and afterAll within describe blocks.

Each hook runs either once for all tests in that describe block, or once for each test (again within that describe block), depending on which one you use.

describe('createBlogPost()', () => {
  let service;

  beforeAll(() => {
    // in this simple  example you could do
    // this inline outside of the beforeAll too
    service = new BlogService();
  });

  beforeEach(() => {
    // Setup before each test in this describe block
    service.clearDatabase();
  });

  afterEach(() => {
    // Cleanup after each test
    service.resetState();
  });

  // also not shown: afterAll()

  test('it creates a blog post', async () => {
    expect(
      await service.createBlogPost({
        title: 'Hello',
      })
    ).toBe(true);
  });
});

If this is not clear- wait until we get to a future lesson just on this topic!

Lesson Task

Using the test runner (use the tabs above), place your test() calls inside describe() blocks.

  • The first argument is a name. It can be any string.
  • The second argument is a function containing your tests.
  • You can nest multiple describe() blocks inside another one.

Ready to try running tests for this lesson?