Search code examples
reactjsunit-testingreact-hooksreact-reduxreact-testing-library

How do I write a test for React Custom Hook that has useAppDispatch and ToolKitSlice?


I have my Custom Hook, it uses useAppDispatch and Slice from ReduxToolkit:

import { useAppDispatch } from "../../app/redux";
import { registrationFormSlice } from "../../entities/registration";
import { getStringToTheTemplate } from "./utils";

export interface IgetFields {
  label: string;
  valueKey: string;
  setValue: any;
  disabled?: boolean;
}

export const useFields = () => {
  const dispatch = useAppDispatch();

  const { setLastName } = registrationFormSlice.actions;

  const getFields = (): IgetFields[][] => {
    return [
      [
        {
          label: "lastName",
          valueKey: "lastName",
          setValue: (value: string) =>
            dispatch(
              setLastName(getStringToTheTemplate(value))
            ),
        },
      ],
    ];
  };
  return getFields;
};

I'm trying to write tests for it:

import { useAppDispatch } from "../../app/redux";
import { registrationFormSlice } from "../../entities/registration";
import { useFields } from "../ForgetPassword/data";

describe("useFields hook", () => {
  let setLastNameSpy: jest.SpyInstance;
  let dispatchMock: jest.Mock;
  let useAppDispatchMock: jest.SpyInstance;

  beforeEach(() => {
    setLastNameSpy = jest.spyOn(registrationFormSlice.actions, "setLastName");
    dispatchMock = jest.fn();
    useAppDispatchMock = jest.spyOn(useAppDispatch, "useAppDispatch");
    useAppDispatchMock.mockReturnValue(dispatchMock);
  });

  afterEach(() => {
    setLastNameSpy.mockReset();
    dispatchMock.mockReset();
    useAppDispatchMock.mockReset();
  });

  it("should call setLastName action with transformed value", () => {
    const getFields = useFields();

    const lastNameField = getFields()[0][0];

    lastNameField.setValue("Test");

    expect(setLastNameSpy).toHaveBeenCalledTimes(1);
    expect(setLastNameSpy).toHaveBeenCalledWith("Test");
    expect(dispatchMock).toHaveBeenCalledTimes(1);
  });

  it("should return correct fields array", () => {
    const getFields = useFields();

    const expectedFields = [
      [
        {
          label: "lastName",
          valueKey: "lastName",
          setValue: expect.any(Function),
        },
      ],
    ];

    expect(getFields()).toEqual(expectedFields);
  });
});

And I get errors:

enter image description here

Code useAppDispatch:

import { AppDispatch, RootState } from "../entities/store";
import { useDispatch } from "react-redux";

export const useAppDispatch = () => useDispatch<AppDispatch>();

Code type AppDispatch:

import { combineReducers, configureStore } from "@reduxjs/toolkit";
import { userSlice } from "./user";
import { registrationFormSlice } from "./registration";

const rootReducer = combineReducers({
  user: userSlice.reducer,
  registrationForm: registrationFormSlice.reducer,
});

export const setupStore = () => {
  return configureStore({
    reducer: rootReducer,
    });
};

export type RootState = ReturnType<typeof rootReducer>;
export type AppStore = ReturnType<typeof setupStore>;
export type AppDispatch = AppStore["dispatch"];

Help solve the errors, please

I tried to find the answer to the problem on the Internet, but all the information I found was about JS and therefore was of little use to me


Solution

  • A comment from Lin Du helped solve my question. My mistake was that I used jest.spyOn() incorrectly:

    useAppDispatchMock = jest.spyOn(useAppDispatch, "useAppDispatch");
    

    The fact is that in just.spyOn( ), the second argument should be the name of the property of the object, and I have an import of a separate specific function.

    Also my test is trying to spy of the registrationFormSlice is superfluous and does unnecessary actions:

    beforeEach(() => {
        setLastNameSpy = jest.spyOn(registrationFormSlice.actions, "setLastName");
        dispatchMock = jest.fn();
        useAppDispatchMock = jest.spyOn(useAppDispatch, "useAppDispatch");
        useAppDispatchMock.mockReturnValue(dispatchMock);
      });
    
      afterEach(() => {
        setLastNameSpy.mockReset();
        dispatchMock.mockReset();
        useAppDispatchMock.mockReset();
      });
    

    For this reason, probably the best working test would be to check the result for structure and data types:

    import { renderHook } from "@testing-library/react";
    import { useFields } from "./data";
    import * as useAppDispatch from "../../app/redux";
    
    describe("useFields", () => {
      const useDispatchMock = jest.spyOn(useAppDispatch, "useAppDispatch");
      useDispatchMock.mockReturnValue(jest.fn());
    
      const setup = () => {
        const { result } = renderHook(() => useFields());
    
        return result.current();
      };
    
      test("Must be an array of arrays", () => {
        const getFields = setup();
    
        expect(Array.isArray(getFields)).toBeTruthy();
        expect(getFields.length).toBe(1);
        expect(useDispatchMock).toBeCalled();
      });
    
      test("An array with index 0 must have certain properties and structure", () => {
        const getFields = setup()[0];
    
        expect(getFields.length).toBe(1);
        expect(typeof getFields[0].label).toBe("string");
        expect(getFields[0].valueKey).toBe("lastName");
        expect(typeof getFields[0].setValue).toBe("function");
      });
    });