DEV Community

Cover image for Three Important Things I Learned While Jest Testing My Next.js Application
Kota Ito
Kota Ito

Posted on

Three Important Things I Learned While Jest Testing My Next.js Application

I am an absolute beginner with Jest, and I've been learning a lot every day.

In this article, I'll jot down five things I've recently learned about Jest testing in Next.js.
Whether it's about mocking or setting up the test environment, I'll write it all down as a note for myself📝.

1: Using renderHook to Test React Custom Hooks

I wanted to test my React custom hook, useAuth, which returns a set of user session data in an object. Initially, I tried to test useAuth like this:

    const { session, status, userId } = useAuth();
    expect(session).toStrictEqual({ user: { id: 'dummyId' } });
    expect(status).toBe('authenticated');
    expect(userId).toBe('dummyId');
Enter fullscreen mode Exit fullscreen mode

However, I encountered the following error:

Warning: Invalid hook call. Hooks can only be called inside of the body of a function component.

To resolve this error, you need to call useAuth using renderHook from @testing-library/react:

import { renderHook } from '@testing-library/react';
// ...
   const { result } = renderHook(() => useAuth());
    const { session, status, userId } = result.current;
Enter fullscreen mode Exit fullscreen mode

Using renderHook correctly calls useAuth within the proper context and returns the result object, allowing you to access session, status, and userId without any errors!

2: Always Remember to mockReset or mockRestore

It is a good practice to always use mockReset or mockRestore to reset mocks between tests to avoid unexpected behavior.

For example, consider the following test cases:

it('when status is unauthenticated, router.push is called', () => {
    mockUseSession.mockReturnValue({
      data: null,
      status: 'unauthenticated',
    });

    renderHook(() => useAuth());

    expect(pushMock).toHaveBeenCalledWith('/auth/signin');
  });
  it('when status is loading, it should return loading as status', () => {
    mockUseSession.mockReturnValue({
      data: null,
      status: 'loading',
    });
    const { result } = renderHook(() => useAuth());
    const { status } = result.current;
    expect(status).toBe('loading');
  });
Enter fullscreen mode Exit fullscreen mode

In these tests, mockUseSession returns different values using mockReturnValue. To ensure that each mock value doesn't interfere with others, you should reset the mock after each test:

  afterEach(() => {
  // Reset mockUseSession to avoid test interference
    mockUseSession.mockReset();
  });
Enter fullscreen mode Exit fullscreen mode

It's important to note that you should **not* use mockRestore in this case above. mockRestore removes the mock and restores the original function. This means that if you use mockRestore, you would lose the mock entirely, and you would need to reapply the mock for each test. This is not ideal if you want to retain the mocked implementation but reset its state between tests.

3: When using Next.Font, don't forget to mock the next/font/google file to avoid issues in your tests.

If you are using Next.Font in your Next.js application, you have to mock the 'next/font/google' file to avoid the following error.

 FAIL  __tests__/hooks/useModal.test.tsx
  ● Test suite failed to run

    TypeError: (0 , google_1.Mulish) is not a function
Enter fullscreen mode Exit fullscreen mode

In your root repository, create the _mocks_ folder and add 'nextFontMock.js' file which contains following code.

import { jest } from '@jest/globals';
module.exports = {

  Staatliches: jest.fn(() => ({
    className: 'mocked-staatliches-font',
  })),
  Mulish: jest.fn(() => ({
    className: 'mocked-mulish-font',
  })),

...add more
};

Enter fullscreen mode Exit fullscreen mode

and add a following lien to jest.config.js(ts)

  moduleNameMapper: {
    ...other settings,
    'next/font/google': '<rootDir>/__mocks__/nextFontMock.js',
  },
Enter fullscreen mode Exit fullscreen mode

With this setting, whenever next/font/google is imported in your tests, Jest will use the mock implementation instead.

Note: This is also useful for mocking specific modules that are complex or unnecessary for the context of your tests.

Conclusion

It is actually harder for me to set up the test environment than testing itself at first 😅
I have gotten so many errors that took me a while to solve and it was so frustrating.
But it is important to keep in mind that testing is necessary for a secure and stable application so I have to keep learning and improving my skills.

Thank you for reading!! 📖

[Cover photo credit]
from:Unsplash
taken by: Hans Reniers

Top comments (0)