React reactive values in unit tests

47 Views Asked by At

I am experimenting with unit tests of react hooks using "React context dependency injection" to be able to better apply test-driven development (after reading two posts about it https://blog.testdouble.com/posts/2021-03-19-react-context-for-dependency-injection-not-state/ and https://medium.com/@matthill8286/dependency-injection-in-react-a-good-guide-with-code-examples-4afc8adc6cdb).

Now I want to fake/mock a hook's reactive return value in order to test another hook and verify that it reacts to changes in its dependencies. I have prepared a stackblitz example: https://stackblitz.com/edit/vitejs-vite-nwro2m?file=src%2FUseUsername.ts

// DependencyContext.ts

import { createContext } from 'react';

export interface Dependencies {
  useUserId: () => string;
}

export const DependencyContext = createContext<Dependencies>(null!);

The hook I want to test is created with a factory in order to inject some preconfigured function that returns a username to a given userId. It also uses useUserId returned from the DependencyContext.

// UseUsername.ts

import { useContext } from 'react';
import { DependencyContext } from './DependencyContext';

export function createUseUsername(userNameApi: (userId: string) => string) {
  return function () {
    const { useUserId } = useContext(DependencyContext);
    const userId = useUserId();
    return userNameApi(userId);
  };
}

Using a factory and the DependencyContext allows for tests to be contained, the hook to be isolated, and no modules required to mock. In the test below, I want to verify that the hook updates its value when the userId changes.

// UseUsername.test.tsx

import { describe, test, expect } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { createUseUsername } from './UseUsername';
import { DependencyContext } from './DependencyContext';
describe('UseUsername', () => {
  test('it returns correct name for a given userId', () => {
    function usernameApiFake(userId: string): string {
      const users: Record<string, string> = {
        userA: 'usernameA',
        userB: 'usernameB',
      };
      return users[userId] ?? '';
    }

    let userId = 'userA';
    const useUserIdFake = () => userId;

    const { result, rerender } = renderHook(
      createUseUsername(usernameApiFake),
      {
        wrapper: ({ children }) => (
          <DependencyContext.Provider value={{ useUserId: useUserIdFake }}>
            {children}
          </DependencyContext.Provider>
        ),
      }
    );

    expect(result.current).toBe('usernameA');

    act(() => (userId = 'userB'));
    rerender(); // I don't want to have to call a rerender

    expect(result.current).toBe('usernameB');
  });
});

The problem is that the hook does not update, if I change the userId. I have to explicitly rerender the hook. My questions:

  • Is there a way to make userId reactive, so that the rendered hook automatically rerenders if userId is changed?
  • Or is explicitly calling rerender equivalent and enough to unit test this hook?
0

There are 0 best solutions below