πŸ’‘ This page contain affiliate links. By making a purchase through them, we may earn a commission at no extra cost to you.
11 Jest Best Practices to Get the Most Out of Your Tests

11 Jest Best Practices to Get the Most Out of Your Tests

Making your unit test suite top notch
Ferenc Almasi β€’ 2022 July 08 β€’ Read time 10 min read
  • twitter
  • facebook

Getting a good unit test suite up and running can be a tricky task, especially given that no one wants to write tests. However, having a good unit test suite in place can save you from a lot of headaches, and can give you more confidence in making changes in your code. However, what should you look after? There are a couple of good practices out there.

Here are 11 different best practices for Jest with code examples to help you get the most out of your unit test suite. You can also apply the below practices to any unit testing framework, not just for Jest specifically. However, the code examples may not apply.


1. Setup your test suite in a separate file

Use your jest.config.js file to set up globals and shared code for your test suite. When you start out, you will likely come across code that needs to be shared across different test files. For this, you want to set up a global configuration file. You can do this by adding the following line to your jest.config.js:

Copied to clipboard!
{
    ...
    setupFilesAfterEnv: ['<rootDir>/setup-jest.js']
}
jest.config.js
rootDir references the root of your project's directory

This will use the setup-jest.js file at the root of your project for your entire test suite. Anything that you define here, will be available across all your tests. You may want to import and configure external dependencies here, or global functions.

Copied to clipboard! Playground
// Configure Enzyme or other libraries
import { configure } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import '@testing-library/jest-dom/extend-expect'

configure({ adapter: new Adapter() })

// Mocking globals
app.user = jest.fn().mockReturnValue({
    name: 'Johhny'
    ...
})
setup-jest.js

2. Use one test file per component

Make sure you separate and you only include one component/class/module per test file.

Once you already set up your environment using a setup file, you want to start writing tests, but when doing so, make sure you organize them in a maintainable manner. You want to separate different tests into different files to make them well separated, keep the files slim, and make them easily maintainable.

Copied to clipboard! Playground
// ❌ Don't
- components.test.js

// βœ”οΈ Do
- Accordion.test.js
- Button.test.js
- Icon.test.js 
Looking to improve your skills? Master Jest from start to finish.
Master Jestinfo Remove ads

3. Organize your tests with describe and test/it

Inside each of your test files, organize your test steps into describe and test/it blocks.

You not only want to separate different components into different files, but you also want to organize your test steps inside your files with different blocks.

Copied to clipboard! Playground
// ❌ Don't
it('Test accordion', () => { .. })
it('Test buttons and icons', () => { .. })

// βœ”οΈ Do
describe('Accordion', () => {
    it('Should open and close on click', () => { ... })
    it('Should be able to set the default state', () => { ... })
})

describe('Button', () => {
    it('Should be able to pass a callback function', () => { ... })
    it('Should be able to set to secondary and tertiary', () => { ... })
})

Use describe to create blocks that group several related tests together, and use test or it to execute individual test cases. If you are further interested in the differences between describe, test and it, make sure you check out the below article.

What is the Difference Between Describe, Test, and It in Jest?

4. Setup and reset commons in beforeEach/afterEach hooks

Use beforeEach and afterEach hook to set up common code for test cases, and reset all mocks after your tests.

After you are done creating the blocks and describing what will be tested, it's time to set up your test cases. But in many cases, you may find yourself duplicating code by setting up mocks or calling functions over again. Luckily, Jest comes with a couple of setup hooks that you can use to separate common functionality out of your test cases.

Copied to clipboard! Playground
// Setup common mocks or function calls in the following hooks
describe('Before and after hooks', () => {
    beforeAll(() => setupOnce())
    beforeEach(() => setupEach())
    afterAll(() => resetOnce())
    afterEach(() => resetEach())
})

There are four different hooks in Jest that can be used for repeating or one-time setups. These are beforeAll, beforeEach, afterAll, and afterEach. The before hooks are usually used for setups, while the after hooks are used for clean-ups. They work similarly, but they are executed differently.

  • beforeAll: Executes code before all tests once.
  • beforeEach: Executes code before each test.
  • afterAll: Executes code after all tests once.
  • afterEach: Executes code after each test.

For example:

Copied to clipboard! Playground
describe('How before and after hooks work', () => {
    // Executes once before the two it blocks
    beforeAll(() => { ... })

    // Executes before the 1st it block, then executes a second time before the 2nd block
    beforeEach(() => { ... })

    it('1st it block', () => { ... })
    it('2nd it block', () => { ... })
    
    // Executes once after the two it blocks
    afterAll(() => { ... })

    // Executes after the 1st it block, then executes a second time after the 2nd block
    afterEach(() => { ... })
})

5. Also test what should not happen

When writing tests, a common practice is to test what a function should do, or a component should render. But you not only want to test the obvious but also edge cases and negative cases. What should not happen? What should not be returned from a function, or what should not be rendered by a component?

Copied to clipboard! Playground
// ❌ Don't
describe('Answer', () => {
  it('should render correctly', () => {
    render(<Answer>42</Answer>)
    expect('42').toBeInTheDocument()
  })
})

// βœ”οΈ Do
describe('Answer', () => {
  it('Should render correctly', () => {
    render(<Answer>42</Answer>)
    expect('42').not.toBeInTheDocument()
  })

  it('Should not render when hidden is set to true', () => {
    render(<Answer hidden={true}>42</Answer>)
    expect('42').not.toBeInTheDocument()
  })
})

In the above example, we are testing a component that can be hidden given if a certain prop is defined. We also want to test for this case. Or if your component conditionally renders data, you want to test what happens if the data is not rendered. The same goes for functions. Don't only test positive, but negative paths as well.

Copied to clipboard! Playground
// ❌ Don't
describe('Validating email addresses', () => {
    it('Should validate email addresses', () => {
        expect(validateEmail('email@email.com')).toBe(true)
        expect(validateEmail('john@doe.net')).toBe(true)
    })
})

// βœ”οΈ Do
describe('Validating email addresses', () => {
    it('Should validate email addresses', () => {
        expect(validateEmail('email@email.com')).toBe(true)
        expect(validateEmail('john@doe.net')).toBe(true)

        expect(validateEmail('email')).toBe(false)
        expect(validateEmail('email@')).toBe(false)
        expect(validateEmail('email@email')).toBe(false)
    })
})

6. Make your tests deterministic

Your tests should not depend on each other. It goes without saying, but in order for your tests to be deterministic, you want to ensure that your tests don't depend on each other. Each test case should be independent of the other and should be able to pass on its own.

Copied to clipboard! Playground
// ❌ Don't
describe('Filtering', () => {
    it('Should open up accordion', () => { ... })
    it('Should check best tutorials', () => { ... })
    it('Should filter for best tutorials', () => { ... })
})

// βœ”οΈ Do
describe('Filtering', () => {
    // Mock everything else that is required for the interaction
    it('Should filter for best tutorials', () => { ... })
})
Looking to improve your skills? Master Jest from start to finish.
Master Jestinfo Remove ads

7. Don’t rely on network calls

Being deterministic means you can't rely on network calls. You not only want to ensure that your tests don't depend on each other, but you also want to ensure that your tests are completely isolated from other factors too. This includes:

  • No network calls are being made
  • There are no dependencies on API calls
  • There are no dependencies on environment variables
  • There are no dependencies on external dependencies

This means if you need to test any functionality that heavily relies on one of the above, you first need to mock the dependency and ensure that their output is always consistent.

Copied to clipboard! Playground
beforeAll(() => {
    const useStaticQuery = jest.spyOn(Gatsby, 'useStaticQuery')

    // Mocking implementation to ensure consistent outcomes
    useStaticQuery.mockImplementation(() => { ... })
})
Mocking a query function in Gatsby

8. Don’t duplicate implementation logic

You might be tempted to copy-paste the code from the function you want to test into your test suite and add some expect here and there. This is especially true when you want to expect multiple times, and you have the urge to simplify it with loops and logic. However, you should rather have duplicated code in your test suite than logic that makes your tests behave differently, given different inputs.

Copied to clipboard! Playground
// ❌ Don't
it('Should return the sum of numbers below 10', () => {
    for(let i = 0; i < 10; i++) {
        const x = i
        const y = i + 1

        if (x + y < 10) {
            expect(sum([i, y])).toBe(i + y)
        }
    }
})

// βœ”οΈ Do
it('Should return the sum of numbers below 10', () => {
   expect(sum[1, 2, 3]).toBe(6)
   expect(sum[3, 3]).toBe(9)
   expect(sum[4, 6]).not.toBe(10)
})

You also introduce a couple of problems with this approach. You might copy the same flaws in the logic over to your test suite which means your test suite will pass, but not because it is working correctly. Rather, it contains the same bug as your function. Always test the outcomes, not the implementation.


9. Don’t test implementation logic

You also don't want to test implementation logic. Whenever you write your unit tests, you care about the outcomes and that the correct values are produced. You care about what is happening, not about how it is happening. Implementations can often change without actually affecting the outcome.

Copied to clipboard! Playground
// ❌ Don't
it('Should call reduce on the array', () => { ... })

// βœ”οΈ Do
it('Should return the sum of values', () => { ... })

Imagine that the implementation you are testing is getting refactored. You want your test suite to be a safety net that can give you confidence in making changes in your code base without breaking anything, not a burden that you have to update for every little change.


10. Run your tests as part of your deployment

Run your test suite before each deployment to ensure you didn't accidentally break any functionality. That way, you can ensure that no bugs are released into a live production site for parts of your application that are already covered by tests.

Preferably, you also want to run your unit tests on each pull request to ensure that changes don't break your existing tests.

Looking to improve your skills? Master Jest from start to finish.
Master Jestinfo Remove ads

11. Bonus: Patch bugs

Last but not least, whenever you discover a bug, it is a good practice to patch it with a quick unit test to avoid regressions. That way, you can ensure that these bugs will never return again.

If you take care of all of these 11 best practices, you can ensure that your unit test suite is top-notch, easy to maintain, and deterministic. Are you looking into extending your test suite with E2E tests? Check out some best practices for it too.

6 Cypress Best Practices for Making Your Tests More Deterministic

Do you have any additional tips on best practices when it comes to Jest? Leave them in the comments below! Thank you for reading through, happy testing! βœ…


  • twitter
  • facebook
Did you find this page helpful?
πŸ“š More Webtips
Mentoring

Rocket Launch Your Career

Speed up your learning progress with our mentorship program. Join as a mentee to unlock the full potential of Webtips and get a personalized learning experience by experts to master the following frontend technologies:

Courses

Recommended

This site uses cookies We use cookies to understand visitors and create a better experience for you. By clicking on "Accept", you accept its use. To find out more, please see our privacy policy.