Three Important Things I Learned While Jest Testing My Next.js Application
Tags: jest, testing, nextjs, react
Published: 2024-05-19 19:49:20

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:

```javascript
    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:

```javascript
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:

```javascript
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 your root repository, create the \__mocks__ folder and add  'nextFontMock.js' file which contains following code.

```javascript
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)

```javascript
  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:<a href="https://unsplash.com/ja/%E5%86%99%E7%9C%9F/three-clear-beakers-placed-on-tabletop-lQGJCMY5qcM?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Unsplash</a>
taken by: <a href="https://unsplash.com/ja/@hansreniers?utm_content=creditCopyText&utm_medium=referral&utm_source=unsplash">Hans Reniers</a>