Testing of useEffect hook with try/catch
Asked Answered
D

3

9

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! :)

Dabble answered 27/5, 2020 at 8:6 Comment(0)
R
5

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
     });
}
Resolutive answered 8/9, 2020 at 9:23 Comment(1)
Decorate the async function with await and the try/catch should work as expected. Otherwise the useEffect will not wait for the async functions to complete.Heartbreak
T
5

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();
}, [])
Transmutation answered 3/12, 2021 at 18:44 Comment(0)
I
0

you need to write your catch block like this:

catch (e) {
     // can do something with e in here
      setStatus(FAILURE);
    }
Illdisposed answered 27/5, 2020 at 8:40 Comment(2)
let me know if this helps or if you need any more help!Illdisposed
Unfortunatelly it is not changing outcome, caling catch is the same as catch (e) Maybe it's some kind of bug but I don't know if mount or act cannot handle thisDabble

© 2022 - 2024 — McMap. All rights reserved.