Repository navigation
renderHook result values with waitFor not working #1030
Description
Activity
@kylebake RNTL implementation of both
renderHook&waitForclosely matches React/Dom Testing Library one (essentially it probably is copy+paste+tweak), with minimal changes due to using React Test Renderer instead of React DOM renderer and not using having Js DOM objects likedocument.If you have a bit of spare time and interest, you could try running diff for
renderHookandwaitForsource files between RNTL & RTL maybe this will shed some light why RTL's version is working and RNTL's version is not.Reacted by Kyle Baker- addedbugSomething isn't workingSomething isn't workinghelp wantedExtra attention is neededExtra attention is needed
on Aug 1, 2022 Happy to help out and dig more into it 👍 I'm in the process of upgrading react-native for our app, which is why I'm uncovering some of this. I'll let you know if I find anything over this week
Reacted by Maciej JastrzebskiA bit more info while I'm still investigating: it definitely appears to be that
renderHookisn't rerendering the hook when asynchronous tasks happen. How I tested this theory:If you take the example hook from my first comment, and test it with this code,
result.currentis always 0:const { result, rerender } = setupHook() setInterval(() => { console.log(`result: ${result.current}`) }, 1000) await waitFor(() => expect(result.current).toBe(10), { timeout: 15000 })
However, running the following test,
waitForhandles the situation as I would expect and passes after 3s:let count = 0 setTimeout(() => { count = 10 }, 3000) await waitFor(() => expect(count).toBe(10), { timeout: 15000 })
I'm going to focus my attention on
renderHookfor the time being, as it seems to be the root issueReacted by hjonassonpierrezimmermannbam commented
on Aug 19, 2022 CollaboratorMore actions@kylebake I think the issue here is not with the renderHook implementation but rather that waitFor isn't able to wait for promises using timers, these promises are never resolved (see this other issue #1067). To fix it you can use fake timers, run the pending timer and then use waitFor to resolve the promise. Regarding waitFor not being able to handle this situation I'm not sure what's causing this but I wonder if it's not rather a jest issue
@pierrezimmermannbam do you mean that
setTimeout(andsetInterval) never gets called when not real timers? Or that they get called butwaitForis somehow not able to observe the results of them being called. Either way, that sounds like a serious bug.The promises are resolving in my use cases, I'm able to set breakpoints and see the code get past the async logic. The tests don't seem to be able to see this, however. I'd also push back on it being a jest issue since I'm able to get all of this working without any code/jest changes if I use
testing-library/react.A super hacky workaround that I've found is to continuously update the component/hook in the test and only then can the
waitForeventually see the assertion. My working example right now:const hook = renderHook(() => useCount()) const timer = setInterval(() => { hook.rerender() }, 500) // now this waitFor will correctly see the result change to 10 await waitFor(() => expect(hook.result.current).toBe(10))Sidenote: I can spend some more time on this issue this weekend, been busy with getting our react native version upgraded to unblock us. I'll get a small repo stood up as well so it's easier for us to test against.
Please try this with @testing-library/react-hooks
it('test', async () => { const { result, waitForNextUpdate } = renderHook(() => useHook()); act(() => { result.current.fetch(); }); expect(result.current.state).toBe(undefined); await waitForNextUpdate(); expect(result.current.state).toBe('GOOD'); //or at least "BAD" });Reacted by Lorenzo Pieri, Christopher Krogh, jesusvallez, YoungHyun Kim, Luciano Lima, Jose Molina, rscarfonepagseguro, Eythan David, Giancarlos Castillo and Robin Grasspierrezimmermannbam commented
on Sep 14, 2022 CollaboratorMore actions@kylebake can you confirm if this issue persists with RNTL v11.2.0?
Reacted by Kyle BakerReacted by Kyle BakerLooks like I'm no longer able to reproduce the issue with v11.2.0, thanks for all the collaboration on this! Apologies for my slow responses, I'll try to help out more moving forward with this repo if I can
Reacted by Mahendra HirapraReacted by LubboAwesome, thanks for checking that @kylebake
Looks like I'm no longer able to reproduce the issue with v11.2.0, thanks for all the collaboration on this! Apologies for my slow responses, I'll try to help out more moving forward with this repo if I can
I'm still reproducing the problem in v11.2.0!
@lubbo could you please post a repro using https://github.com/callstack/react-native-testing-library/tree/main/examples/basic please? Without that, we can't figure out what is happening, since the issue has been solved for most (it seems) users.
Are you using react 18 by the way? Because there could still be problem with older versions of react, but I'm not sure we'll be able to prioritise work to fix them.
Hi @AugustinLF here is my repro using the basic example: https://github.com/bkdev98/rn-testing-lib-waitfor-issue. The tests/asyncHooks.spec.tsx failed and
waitFordoes not await for new state to be updated.
https://github.com/bkdev98/rn-testing-lib-waitfor-issue/blob/main/__tests__/asyncHooks.spec.tsx
Test result:

@bkdev98 you should use
waitForwith an expectation, i.e. something that throws when the expectation is failed. Returningfalsedoes not work, aswaitForconsiders that a legit return value.That should be relatively easy to fix:
await waitFor(() => expect(result.current).toBeTruthy());
@bkdev98 if that does not fix the issue for you, please adjust your code to use
exceptas shown above and create a new GH issues for your case.Reacted by Khanh Bui, Georgekutty Antony and Brian Hopierrezimmermannbam commented
on Nov 2, 2022 CollaboratorMore actions@bkdev98 I made the following changes in your test and it now works
describe("useProductDetail", () => { beforeEach(() => { jest.useFakeTimers(); }); test("should instantly show initial product data", async () => { jest.useFakeTimers(); const { result } = renderHook(() => useTestAsyncHook()); await waitFor(() => expect(result.current).toBe(true), { timeout: 2500 }); expect(result.current).toEqual(true); }); });
As @mdjastrzebski mentioned the callback provided to waitFor should throw when the expectation is not met and also since you have a setTimeout of 2000ms, you need to increase the waitFor timeout which is of 1000ms per default so that fake timers are advanced by 2000ms, else it will timeout after 1000s
Reacted by Khanh Bui, Alexander Glassford, BreamIsAFish and Damien WhiteReacted by Khanh Bui, Guido Glielmi, BreamIsAFish and mihailminderapierrezimmermannbam commented
on Nov 2, 2022 CollaboratorMore actionsWe probably should revisit the documentation on waitFor, it says that the default timeout is 4500ms which is false and it does not clearly state that the expectation should throw so that it may work. Also I think it would be nice to mention that it behaves differently when using fake timers
Reacted by Augustin Le Fèvre, Alexander Glassford and Stephen BelyeaIt works!! Thank you for your help! ❤️
Reacted by Pierre Zimmermann and Alexander Glassford@pierrezimmermannbam would you have time to update
waitFordocumentation around timeout and making more explicit how the API works (throwing vs boolean)?pierrezimmermannbam commented
on Nov 2, 2022 CollaboratorMore actions@mdjastrzebski sure I'll try to submit a pr by the end of the week
Reacted by Augustin Le FèvreHere's my solution. I have a set of reusable test utils. As my hooks returned multiple values including
nullwhich seems to be the default, it was not an option to wait for null.import { act, render, renderHook } from "@testing-library/react"; import { Suspense, useEffect } from "react"; import type { RenderHookOptions, RenderHookResult, RenderOptions, RenderResult, } from "@testing-library/react"; // Helper function to wrap components with Suspense. function makeWrapper(resolve: () => void) { // Calls `resolve` when the component unsuspends, allowing the test to wait for the state to be resolved. function Resolver(): undefined { useEffect(resolve); } return function Wrapper({ children }: { children: React.ReactNode }): React.ReactElement { return ( <Suspense fallback={null}> {children} <Resolver /> </Suspense> ); } } export async function renderWrapped( ui: React.ReactNode, options?: RenderOptions, ): Promise<RenderResult> { const { promise, resolve } = Promise.withResolvers<void>(); const result = await act(() => render(ui, { ...options, wrapper: makeWrapper(resolve), }), ); await promise; return result; } export async function renderHookWrapped<TProps, TResult>( callback: (props: TProps) => TResult, options?: RenderHookOptions<TProps>, ): Promise<RenderHookResult<TResult, TProps>> { const { promise, resolve } = Promise.withResolvers<void>(); const result = await act(() => renderHook(callback, { ...options, wrapper: makeWrapper(resolve), }), ); await promise; return result; }
Describe the bug
Using the new
renderHookfunction included in this library does not behave the same way when it comes to awaiting different return values as the other implementations (referencing specificallyreact-hooks+testing-library/react).Trying to
waitFordifferent values to change from the hook directly does not seem to be functioning as expected.Expected behavior
If https://github.com/testing-library/react-hooks-testing-library is going to be deprecated in favor of this library, I'd expect to retain the same functionality the previous library offered since it was created to test complex hooks without the need for testing the components using these hooks as well. I'd also expect similar functionality to the
testing-library/reactimplementation.waitForshould be able to handle waiting for the values of the hook to match the expectation passed to itSteps to Reproduce
Consider the following hook:
If we wanted to try and
waitForcount to be 10, there doesn't appear to be a way to do this withtesting-library/react-nativeas we can with the others:The above example will work just fine in
testing-library/react, however.Screenshots
Versions