React: mock hooks in unit tests

Recently I came across the situation where I built a custom hook to check for some keycloak rules that used the useKeycloak hook from the @react-keycloak/ssr library. As you can imagine, the library does all sort of stuff with cookies, local storage, etc. to provide the data from the browser session, but I don't know or care about implementation details, and neither should you. Unit tests should always isolate as much side effects as possible and so I set out to write a test which can mock the useKeycloak hook.

Fortunately jest provides us with the spyOn function to mock functions (after all to the javascript runtime, hook are only functions), without the need to modify the hooks to be testable (with one minor exception, more on that later.

So let's create a simple example that demonstrates the principle. We have two hooks, useExternal, which we want to test and useInternal which is used by useExternal and can do anything from accessing the browser session to fetching data from an api, which we want to mock in our tests. For this example though it only returns a number

useExternal.ts

export const useInternal = ()=>{
  return -1
}

useInternal.ts

import {useInternal} from "./useInternal";

export const useExternal = ()=>{
  const number = useInternal()
  return number *2
}

As stated earlier, there is one important detail: the two hooks need to be in two different files, this is due the way how spyOn works. You can use jest.spyOn(require('./module'), 'exportedMember') to tell jest to replace the export exportedMember from the file module.ts, but only if that function gets imported somewhere, so keep that in mind.

The whole test can look something like this: useExternal.spec.ts

import {useExternal} from "./useExternal";

const useInternal = jest.spyOn(require('./useInternal'), 'useInternal')
useInternal.mockReturnValue(2)

test('useExternal', ()=>{
  const actual = useExternal()
  const expected = 4
  expect(actual).toBe(expected)
})

And after running the test you will see that the mocked value 2 got used and the result is indeed 2*2=4

You can also find the code on github