Darek Gala
Darek Gala

Reputation: 177

Testing of useEffect hook with try/catch

I need to test a catch when the fetching data request rejects but I don't understand why the error is not being caught and I get this error:

UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)

I have a situation like this:

export const Container = ({fetchFirstAsset, fetchSecondAsset}) => {
  const [status, setStatus] = useState(null);

  async function fetchAssets() {
    setStatus(IN_PROGRESS);

    try {
      await fetchFirstAsset();
      await fetchSecondAsset()

      setStatus(SUCCESS);
    } catch {
      setStatus(FAILURE);
    }
  }

  useEffect(() => {
    fetchAssets();
  }, []);

  ....
};

And I test like this:

import {mount} from 'enzyme';
import {act} from 'react-dom/test-utils';

const fetchFirstAsset = jest.fn();
const fetchSecondAsset = jest.fn();

it('should render without errors', async () => {
  fetchFirstAsset.mockReturnValueOnce(Promise.resolve());
  fetchSecondAsset.mockReturnValueOnce(Promise.reject());
  let component;

  await act(async () => {
    component = mount(
      <Container
        fetchFirstAsset={fetchFirstAsset}
        fetchSecondAsset={fetchSecondAsset}
      />
    );
  });

  expect(fetchSomething).toHaveBeenCalled();
});

If I test the case when fetchSomething resolves with Promise.resolve() everything works fine and the tests pass, but when I try to Promise.reject() in order to test the catch case then this error is not caught and I have unhandled promise rejection.

(If you are wondering why code looks like this: In other places in the app I handle changing of status with redux, so testing of catch is easy, but in one place I need to fetch 3 different assets for the component and I decided to handle the change of status with useState because extracting 3 different statuses from redux and combining it will be ugly. With useState is much cleaner I think)

Thanks in advance for help! :)

Upvotes: 10

Views: 24556

Answers (3)

Hugo Moraes Bonatto
Hugo Moraes Bonatto

Reputation: 61

That's a good one, You need to declare your function inside the useEffect() and implement the try/catch block inside of it, and outside of the function you only call it

(also, don't forget to cleanup your useEffect())

useEffect(() => {
  const fetchAssets = async () => {
    setStatus(IN_PROGRESS);

    try {
      await fetchFirstAsset();
      await fetchSecondAsset()

      setStatus(SUCCESS);
    } catch {
      setStatus(FAILURE);
    }
  }

  fetchAssets();
}, [])

Upvotes: 6

Rolento
Rolento

Reputation: 61

I had the same problem whereby the try/catch clause does not work when used within useEffect(). I did a few searches and it appears this is a potential bug, take a look at:

https://github.com/testing-library/react-hooks-testing-library/issues/305

That said, I was able to address the issue as follows:

FAILURE EXAMPLE:

useEffect(() => {
  try {
      invokeMyAsyncFunction();  
      setStatus(SUCCESS);
  } catch (e) {
      setStatus(FAILURE);   // <== will not be invoked on exception!!
  }
}

SUCCESS EXAMPLE:

useEffect(() => {
  invokeMyAsyncFunction()
     .then((response:any) => {
        setStatus(SUCCESS);
     })
     .catch((e) => {
        setStatus(FAILURE);   // <== this **WILL** be invoked on exception
     });
}

Upvotes: 6

Red Baron
Red Baron

Reputation: 7652

you need to write your catch block like this:

catch (e) {
     // can do something with e in here
      setStatus(FAILURE);
    }

Upvotes: 0

Related Questions