2

Look at the following custom hook. The premise is that it updates its state when query changes.

export function UseCustomHook() {
  const { query } = useRouter()
  const [state, setState] = useState({})

  useEffect(() => {
    const updatedState = helperFunction(query)
    setState(updatedState)
  }, [query])

  return state
}

The goal is to mock useRouter but then also update it to test specifically "helper function is called n-number of times when query updates n-number of times"

We can mock the module useRouter with

jest.mock('next/router', () => ({
  useRouter() {
    return {
      route: '/',
      pathname: '',
      query: {...},
      asPath: '',
    }
  },
}))

But this simply mocks it as a normal module. I want to mock it as a hook and then update it within the test below

describe('useCustomHook', () => {
    it('should call helperFunction when query updates', () => {
        const query = {...}
        jest.spyOn(Router, 'useRouter' as any).mockImplementation(() => ({ query }))
        
        const { result } = renderHook(() => useCustomHook())
        expect(...)  
    })
  })
  

1 Answer 1

4

You can use jest.mock() to mock next/router module, useRouter hook and its return value. After we changed the query value, we should call rerender function to rerender the custom hook, so that the useEffect hook will use the new query as its dependency.

Besides, I use jest.spyOn() to add spy on console.count() method to check how many times the effect function calls.

E.g.

useCustomHook.ts:

import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';

export function useCustomHook() {
  const { query } = useRouter();
  const [state, setState] = useState({});

  useEffect(() => {
    console.count('effect');
    setState({ id: query.id });
  }, [query]);

  return state;
}

useCustomHook.test.ts:

import { useRouter } from 'next/router';
import { renderHook } from '@testing-library/react-hooks';
import { useCustomHook } from './useCustomHook';
import { mocked } from 'ts-jest/utils';
import { NextRouter } from 'next/dist/next-server/lib/router/router';

jest.mock('next/router');

const useMockRouter = mocked(useRouter);

describe('68660313', () => {
  test('should pass', () => {
    const countSpy = jest.spyOn(console, 'count');
    const query1 = ({ query: { id: '1' } } as unknown) as NextRouter;
    const query2 = ({ query: { id: '2' } } as unknown) as NextRouter;

    useMockRouter.mockReturnValue(query1);
    const { result, rerender } = renderHook(() => useCustomHook());
    expect(result.current).toEqual({ id: '1' });
    useMockRouter.mockReturnValue(query2);
    rerender();
    expect(result.current).toEqual({ id: '2' });
    expect(countSpy).toBeCalledTimes(2);
  });
});

test result:

 PASS  examples/68660313/useCustomHook.test.ts (7.664 s)
  68660313
    ✓ should pass (32 ms)

  console.count
    effect: 1

      at console.<anonymous> (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)

  console.count
    effect: 2

      at console.<anonymous> (node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866:25)

------------------|---------|----------|---------|---------|-------------------
File              | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------------|---------|----------|---------|---------|-------------------
All files         |     100 |      100 |     100 |     100 |                   
 useCustomHook.ts |     100 |      100 |     100 |     100 |                   
------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        8.184 s

packages' version:

"next": "^11.0.1",
"jest": "^26.6.3",
"ts-jest": "^26.4.4",
"react": "^16.14.0",
Sign up to request clarification or add additional context in comments.

1 Comment

ts-jest dropped mocked because it was included in jest. jestjs.io/docs/jest-object#jestmockedsource-options

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.