Search code examples
javascriptreact-nativejestjsreact-hooksreact-hooks-testing-library

The current result of a custom hook is updated when testing using react-hooks-testing-library


I am testing the custom React Hook shown below. Its functionality is to get the size of an image and then use the props provided to calculate the size of the image that the user wants.

import { useEffect, useState } from 'react';
import { Image, LayoutAnimation, useWindowDimensions } from 'react-native';

export type SizeType =
  | {
      width: number;
      height: number;
    }
  | undefined;

export const useImageSize = (imageUri: string, fillWidth?: boolean, fillHeight?: boolean, width?: number, height?: number): SizeType => {
  const [size, setSize] = useState<SizeType>();

  const { width: screenWidth, height: screenHeight } = useWindowDimensions();

  useEffect(() => {
    Image.getSize(
      imageUri,
      (w, h) => {
        LayoutAnimation.easeInEaseOut();

        if (fillWidth) {
          setSize({
            width: screenWidth,
            height: h * (screenWidth / w),
          });

          return;
        }

        if (fillHeight) {
          setSize({
            height: screenHeight,
            width: w * (screenHeight / h),
          });

          return;
        }

        if (width && !height) {
          setSize({
            width,
            height: h * (width / w),
          });

          return;
        }

        if (!width && height) {
          setSize({
            width: w * (height / h),
            height: height,
          });
        }
      },
      () => {
        setSize({ width: 0, height: 0 });
      }
    );
  }, [imageUri, fillWidth, fillHeight, screenWidth, screenHeight, width, height]);

  console.log('size', size);

  return size;
};

This is the test for this hook. It mocks the getSize method of the Image component and it also mocks the useWindowDimension hook from React Native.

import * as RN from 'react-native';
import { renderHook, act } from '@testing-library/react-hooks';
import { useImageSize } from '@app/hooks/useImageSize';

describe('useImageSize', () => {
  const getSizeSpyOn = jest.spyOn(RN.Image, 'getSize').mockImplementation(jest.fn());

  afterEach(() => {
    jest.clearAllMocks();
  });

  it('should return the correct size when we want to fill the width of the screen', () => {
    jest.spyOn(RN, 'useWindowDimensions').mockReturnValue({ width: 200, height: 200, fontScale: 1, scale: 1 });
    const { result } = renderHook(() => {
      useImageSize('test_image', true, false)
    });

    expect(getSizeSpyOn).toHaveBeenCalledTimes(1)

    const getSizeArgs = getSizeSpyOn.mock.calls[0]!;
    expect(getSizeArgs[0]).toBe('test_image');

    const success = getSizeArgs[1];

    void act(() => {
      success(100, 50);
    });

    console.log('result.current', result.current)

    expect(result.current).toEqual({
      width: 200,
      height: 100,
    });
  });
    
    console.log('result.current', result.current)

    expect(result.current).toEqual({
      width: 200,
      height: 100,
    });
  });
});

After I run the test, the console log in the hook changes from undefined to { width: 200, height: 100 } which is the expected value, however, the console log in the test is still undefined. I think this behaviour indicates that during the test, the hook behaves as expected, it is just for some reason the current value of the hook in the test is not updated after the new value is available.

Does anyone know how to solve this?

enter image description here


Solution

  • The callback must return the result of the hook, not just call it. Change this line:

        const { result } = renderHook(() => {
          useImageSize('test_image', true, false)
        });
    

    to this:

        const { result } = renderHook(() =>
          useImageSize('test_image', true, false)
        );
    

    Note that I have removed the {...} in the callback.