Unit Test useFetch Hook

100 Views Asked by At

I am getting an Object.dispatchError when I run my unit test for my custom useFetch hook.

The hook:

import { useState, useEffect } from 'react';

const useFetch = (url) => {

  const [data, setData] = useState('');
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);

    const apiCall = async () => {
      try {
        const res = await fetch(url);
        if (!res.ok) throw new Error(`Error - ${res.status}`);
        const json = await res.json();

        setData(json);
        setError(null);
      } catch (err) {
        setError(`useFetch Error!`);
      } finally {
        setLoading(false);
      }
    };

    apiCall();
  }, [url]);

  return { data, loading, error };
};

export default useFetch;

My unit test:

  import { renderHook, waitFor } from "@testing-library/react";
  import useFetch from './useFetch';

  it('Should handle no URL provided', async () => {
    const { result } = renderHook(() => useFetch());

    await waitFor(() => {
      expect(result.current.error).toBe("useFetch Error!");
    });
  });

The error:

console.error
Error: AggregateError
  at Object.dispatchError (jsdom\living\xhr\xhr-utils.js:63:19)
  at Request.<anonymous> (jsdom\lib\jsdom\living\xhr\XMLHttpRequest-impl.js:655:18)
  at Request.emit (node:events:531:35)
  at ClientRequest.<anonymous> (jsdom\lib\jsdom\living\helpers\http-request.js:121:14)
  at ClientRequest.emit (node:events:519:28)
  at Socket.socketErrorListener (node:_http_client:492:9)
  at Socket.emit (node:events:519:28)
  at emitErrorNT (node:internal/streams/destroy:169:8)
  at emitErrorCloseNT (node:internal/streams/destroy:128:3)
  at processTicksAndRejections (node:internal/process/task_queues:82:21) undefined

Maybe it is my waitFor() in the unit test that is not written correctly? How should I unit test my custom hook?

Cheers!

StackBlitz: useFetch Custom Hook

(Although, how you can run unit tests in stackblitz is beyond me...)

1

There are 1 best solutions below

6
mandy8055 On

You've not provided a URL to your useFetch hook, so it's trying to make a request to an undefined URL. The error you're getting is related to the request itself, not the error handling within your hook.

One way would be to mock the fetch function to avoid making actual requests and simulate different scenarios for your hook. Something like:

// ...your imports

// Mock the fetch function
global.fetch = jest.fn(() =>
  Promise.resolve({
    ok: true,
    json: () => Promise.resolve({}),
  })
);
// other tests...

it('Should handle no URL provided', async () => {
  // Clear any previous mock calls
  fetch.mockClear();

  // Set the fetch mock to reject with an error
  fetch.mockImplementationOnce(() => Promise.reject('Failed to fetch'));

  const { result, waitForNextUpdate } = renderHook(() => useFetch(undefined));

  // waited for error to happen here
  await waitFor(() => result.current.error !== '');

  expect(result.current.error).toBe("useFetch Error!");
});

Sidenote: You can also leverage the msw package to write more accurate unit tests for APIs without resorting to manual mocking. MSW uses service workers behind the scenes to intercept API requests and inject test data before making the actual calls.