-
Notifications
You must be signed in to change notification settings - Fork 232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Testing the use of Promises with setTimeout in useEffect hook #241
Comments
I'm not very familiar with mocking timers myself, but I think if you have called
If |
@mpeyper The test is not passing by just running the timers. Just to reiterate, the test fails if I try to await the promise in this function used in const run = async () => {
// waiting for the promise and having a setTimeout causes the test to to fail
await Promise.resolve();
setTimeout(() => {
setCount(count => count + 1);
}, 5000);
}; |
Hmm, ok. I'll take a look after the kids go to bed tonight. |
Ok, so I know why it isn't working. It basically boils down to when Your test follows the following sequence of events:
The deadlock occurs here because My initial reaction, was oh, that's easy, I'll just wait first for the promise first, then run the timers, but unfortunately this also doesn't work because there is not My next thought was that I could use one of the other async utils, const { result, waitForValueToChange } = renderHook(() => Counter());
await waitForValueToChange(() => {
jest.runAllTimers();
return result.current.count;
});
expect(result.current.count).toEqual(1); Unfortunately, it still times out. This time it's because I forgot that both Finally, I was able to get the test to pass by delaying when const { result, waitForNextUpdate, waitForValueToChange } = renderHook(() => Counter());
setImmediate(() => {
act(() => {
jest.runAllTimers();
});
});
await waitForNextUpdate();
expect(result.current.count).toEqual(1); Now the test follows this sequence of events:
This works, but is very brittle for changes to the hook's flow and is definitely testing implementation details (which we should try to avoid). I'm not 100% sure how to proceed on this one. The I'll think on this and I'm happy to take suggestions and feedback in this issue. |
Thank you for @mpeyper ! |
I was having trouble as well, specifically with setInterval inside a useLayoutEffect. Extra information
Relevant code or config:In the git repo. What you did:I ran a setInterval inside a useLayoutEffect (same problem with useEffect) hook and tried to advance it with jest.advanceTimersToNextTimer and jest's mock timers. What happened:Reproduction:No codesandbox (jest.useFakeTimers is not implemented there) but I have a repo. |
anyone knows how to properly test these kind of implementations? |
@giacomocerquone can you elaborate on what your hook/test look like? For what it's worth, I've made a start on #393 so some of the issues will go away soon, but the chicken and egg problem of triggering an update while waiting for the change is unlikely to result in a a clean reading test. Open to idea on how you'd like to write your test, and see if we can make something work along those lines. |
@mpeyper sorry but I'm too busy at work, if it's still needed I can recreate a repro |
Yes please. Perhaps raise a new issue when you have time and I'll dig into the specifics of your situation there. |
UseDelayEffect hook test. Hook is changing false on true with timeout
|
Hi @vladrose, Can you share the |
There didn't ever appear to be a clear, concise answer as to how to rectify the original posters solution. Here is what 45 minutes of excessive digging has produced: Code Behind: import { useState, useEffect } from 'react';
function OmnipresentTimer() {
// State to retain the time spent on App
const [timeOnApp, setTimeOnApp] = useState<number>(0);
// Use a continous looping useEffect to create a timer for how long someone has been on the app, in seconds.
useEffect(() => {
let timer = setInterval(() => {
setTimeOnApp(timeOnApp + 1);
}, 1000);
return () => clearInterval(timer);
})
return { timeOnApp }
}
export default OmnipresentTimer; Our test code: import { act, renderHook } from '@testing-library/react-hooks';
import OmnipresentTimer from '../OmnipresentTimer';
afterEach(() => {
jest.clearAllMocks()
})
describe("App holds a continuous timer tick", () => {
test("if after a second has passed, the interval has increased", async () => {
const { result, waitForValueToChange } = renderHook(() => OmnipresentTimer())
act(() => {
jest.useFakeTimers();
});
expect(result.current.timeOnApp).toEqual(0);
await waitForValueToChange(() => {
return result.current.timeOnApp;
});
expect(result.current.timeOnApp).toEqual(1);
})
}) It seems the key things to take away from this are:
I'm fairly new to unit testing, so would appreciate any rectification surrounding my findings. But from what i've gathered. It checks out. |
@Bizzle-Dapp I haven't looked closely at or ran your code, but a few things from just reading you comment:
So with all that, my (untested) hook would look like import { useState, useEffect } from 'react';
function useOmnipresentTimer() {
// State to retain the time spent on App
const [timeOnApp, setTimeOnApp] = useState<number>(0);
// Use a continous looping useEffect to create a timer for how long someone has been on the app, in seconds.
useEffect(() => {
let timer = setInterval(() => {
setTimeOnApp(time => time + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
return { timeOnApp }
}
export default useOmnipresentTimer; And my (untested) test: import { renderHook } from '@testing-library/react-hooks';
import useOmnipresentTimer from '../OmnipresentTimer';
describe("App holds a continuous timer tick", () => {
test("if after a second has passed, the interval has increased", async () => {
const { result, waitForValueToChange } = renderHook(() => useOmnipresentTimer());
expect(result.current.timeOnApp).toBe(0);
await waitForValueToChange(() => result.current.timeOnApp);
expect(result.current.timeOnApp).toBe(1);
})
}) |
Hmm, removing the fakeTimers like in your code causes the test to time out. All of the other advice us sage though, my man. Many thanks! |
Ah, that's because the default timeout on await waitForValueToChange(() => result.current.timeOnApp, { timeout: 2000 }); Or just disabling the feature all together: await waitForValueToChange(() => result.current.timeOnApp, { timeout: false }); |
Legend. Works an absolute charm. Thanks for clarifying all of that! 🐱👤 |
Hey there! I'm having an issue testing a custom hook that uses an async function in the useEffect hook. If I try to await a promise inside of the
run
function, my test times out if I usewaitForNextUpdate
. What am I doing wrong and how can I fix this behavior?The text was updated successfully, but these errors were encountered: