Mocking and testing onError with parameters

Viewed 1488

I am trying to mock useMutation, It has a certain parameter called onError. I am using it as depicted in the piece of code right below.

Whenever useRedirectOnErrorMutation is used, a header with the token is attached to the request, so if the token is invalid there will be 401 error response which I will catch in the onError function that comes with useMutation and redirect the user to the login page.

export const useRedirectOnErrorMutation = <
    TData = unknown,
    TError = unknown,
    TVariables = void,
    TContext = unknown
>(
    redirectionPath: string,
    key: MutationKey,
    mutationFunction: MutationFunction<TData, TVariables>,
    options?: UseMutationOptions<TData, TError, TVariables, TContext>
): UseMutationResult<TData, TError, TVariables, TContext> => {
    const router = useRouter();
    console.log(useMutation);
    return useMutation(key, mutationFunction, {
        ...options,
        onError: (error) => {
            if (error?.response.status == "401" || error?.response.status == "422") {
                localStorage.removeItem("token");
                localStorage.removeItem("userId");
                router.push(redirectionPath);
            }
        },
    });
};

The above code works perfectly fine, now I have to test this function with 100% coverage.

My current implementation on the testing of the function is as follows.

describe("(when api call gives error", () => {
        it("should return the response", () => {
            const push = jest.fn();

            let onError: (
                error: any,
                variables: any,
                context: any | undefined
            ) => Promise<void> | void;
            // @ts-ignore
            Router.useRouter = jest.fn(() => ({
                push: push,
            }));
            const mockData = { email: "test@test.com", password: "test@1234" };

            // @ts-ignore
            ReactQuery.useMutation = jest.fn(
                (
                    key: MutationKey,
                    mutationFunction: MutationFunction,
                    options: MutateOptions<
                        AxiosResponse<LoginResponse>,
                        AxiosError,
                        LoginValidationValues
                    >
                ) => {
                    console.log(options.onError.arguments);
                    onError = options.onError;
                    return {
                        mutate: jest.fn(() => mockData),
                        isLoading: false,
                    };
                }
            );
            const { result, rerender } = renderHook(() =>
                useRedirectOnErrorMutation("/auth/admin/login", "key", adminLoginAPI)
            );

            rerender();

            act(() => {
                onError({}, {}, {});
            });

            expect(result.current.mutate(mockData)).toEqual(mockData);
            expect(push).toHaveBeenCalled();
        });
    });
});

Now, the issue with testing here is that everytime I run it, it still shows the onError part uncovered and an error message saying that error.response is not defined. I am not sure how to pass the onError parameters in the testing.

That is (error)

onError: (error) => {
                if (error?.response.status == "401" || error?.response.status == "422") {
                    localStorage.removeItem("token");
                    localStorage.removeItem("userId");
                    router.push(redirectionPath);
                }

Ideally passing an error object in the testing wrapped by act should have worked but it also ended up with an error message onError is not a function.

act(()=>{
  onError({response:{status: 401}},{},{})
})

Any help would be appreciated! I am completely stuck here.

2 Answers

I can't really give you a specific answer, just maybe a bit of advice:

  1. what is the purpose behind the need to have 100% test coverage? The function you are trying to test has many side effects, and you have to mock things like the request, localStorage, react-router, onError, ... and what remains is basically more testing code than actual production code. You are basically testing if react-query calls the onError method, and this is something that react-query tests internally. Don't test 3rd party code - test your own code. Things like this are much better tested with integration or end to end tests, cypress is a great tool for that.

  2. If you want to test custom hooks, I would only mock the result of the queryFn, not the whole mutation function. nock is good for that - there is even an example of how to to this in the react-query-docs. mock-service-worker is a great tool to mock requests on the network level, and I have recently written an article about testing react query with mock service worker if you want to give it a read here. It also links to an example repo with working tests.

Related