react-hooks-testing-library: Spy on console.error doesn't work
react-hooks-testing-libraryversion: 5.0.3reactversion: 16.13.1nodeversion: 14.15.4
Relevant code or config:
it('should abort request if component is unmounting', async () => {
// Given
const consoleErrorSpy = jest.spyOn(global.console, 'error');
const { result, unmount } = renderHook(() => useFetch('key', fetchFn));
// When
unmount();
// Then
expect(fetchFn).toHaveBeenCalledTimes(1);
expect(result.current.status).toBe('pending');
expect(consoleErrorSpy).not.toHaveBeenCalled();
});
What you did:
I try to make a test failed when I get a React warning. For example, I run a race condition in my source code and I get the following error in console : Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
What happened:
This test always passed, even if the console.error is shown in the terminal so it’s a false positive test.
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 1
- Comments: 26 (14 by maintainers)
Asserting something didn’t occur is always tough because there is other way (that I’m aware of) than just waiting long enough for that you’re confident an update would have happened if it was going to happen. That’s what this solution does. It sets up an update to occur in 200ms, then it unmounts the component and waits for 250ms, then check that no logs were produced.
If an update happens (can only occur if the component failed to unmount for some reason - I’m not sure if that’s even possible),
waitForNextUpdatewontreject, so that assertion will fail. If and update was attempted on the unmounted component, the log will be produced at around 200ms andwaitForNextUpdatewill reject at around 250ms, passing that assertion but failing the next one checkingconsole.errorspy.It might have been possible to get this test working with a fake timer, but In general I don’t like faking timers. I’ve seen more cases where the tests get harder to read or become fiddly to get passing than I’ve seen the extra 250ms of test runtime become an issue, plus it gives me more confidence that it’s going to work in the real world where time can’t be controlled by calling a function.
My mistake, I was forget the
consoleSpyErrorexpect just after the waitForNextUpdate. This solution is very acceptable, thank you, that is what I want to achieve.Async utils are very difficult to understand. I’m not sure to understand completely the solution anyway but you teach me a new way to handle timers with RTL.
That is not what I’m seeing…
Flag commented out:
Flag left in:
This is with my
waitForNextUpdateversion. replacing the test with yourwaitForValueToChangeversion does produce the false positive.@ludovicmnji please note that in my example, I had removed the
jest.useFakeTimers("modern");line from the test (so the timeout would fire itself).the console.error isnt an issue, so it’s really hard to help you write your test if all the bits aren’t there to actually write the test for.
There are loads of examples in closed issues how to tackle what you want to do, here’s one: https://github.com/testing-library/react-hooks-testing-library/issues/425 this person uses manual mocks you could mock react’s useReducer function & test that
dispatchdoes not fire. This would be more reliable.I don’t think sandbox is the best place for testing, it seems like
mock&useFakeTimersdon’t work. see here – https://github.com/codesandbox/codesandbox-client/issues/513. So, i’ve redone the sandbox in a repo here – https://github.com/joshuaellis/rhtl-555 and the test passes with no errors.Maybe i’m missing something?