Learn the basics of how to test with Jest or Vitest. This course covers the main features for writing assertions, tests, mocks, async testing, and more
Learn the basics of how to test with Jest or Vitest. This course covers the main features for writing assertions, tests, mocks, async testing, and more
Testing Fundamentals: 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)
code
// 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).
code
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.
code
describe('createBlogPost()',()=>{let service;beforeAll(()=>{// in this simple example you could do// this inline outside of the beforeAll too service =newBlogService();});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 aboveon the right), 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.