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

RMAG news

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);

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;

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);
});

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();
});

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

In you 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
};

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

moduleNameMapper: {
other settings,
next/font/google: <rootDir>/__mocks__/nextFontMock.js,
},

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