Skip to content

No waitForNextUpdate ? #1101

Closed as not planned
Closed as not planned

Description

@kamilzielinskidev

Hi all,

I've briefly checked the docs and discord to check that and it sees that waitForNextUpdate is not available as return of renderHook for now in testing library.
Is there a plan to implement that or testing async hooks is considered as bad practice?

If it won't be implemented maybe theres an option to inform about it in the docs, as it seems to be main feature for testing async hooks

Thank you for what you do with the lib.

Activity

  1. eps1lon commented on Jul 28, 2022

    @eps1lon
    Member

    Is there a plan to implement that or testing async hooks is considered as bad practice?

    the next update isn't necessarily relevant to the user. We want to discourage testing these implementation in Testing Library. waitFor has all the tools you need.

  2. srosato commented on Sep 15, 2023

    @srosato

    @eps1lon is right on the waitFor, though since we are testing hooks, sometimes it might be harder to test what is relevent to a user since testing a hook is arguably in some way testing implementation details.

    Here is an example of an upgrade I did (code simplified).

    From:

    import { renderHook } from '@testing-library/react-hooks';
    
    describe(`Search users`, () => {
      let store: Store;
    
      const searchTerm = 'test';
    
      beforeEach(() => {
        store = createTestStore();
      });
    
      it(`searches for users`, async () => {
        const wrapper = <ReduxProvider store={store}>{children}</ReduxProvider>
        const { result, waitForNextUpdate } = renderHook(() => useSearchUsers(searchTerm), { wrapper });
    
        expect(result.current).toMatchObject({ users: [], isFetching: true });
    
        // OLD WAY
        await waitForNextUpdate();
    
        expect(result.current).toMatchObject({ users: [], isFetching: false });
    
        // assert more...
      });
    });

    To:

    import { renderHook, waitFor } from '@testing-library/react';
    
    describe(`Search users`, () => {
      let store: Store;
    
      const searchTerm = 'test';
    
      beforeEach(() => {
        store = createTestStore();
      });
    
      it(`searches for users`, async () => {
        const wrapper = <ReduxProvider store={store}>{children}</ReduxProvider>
        const { result } = renderHook(() => useSearchUsers(searchTerm), { wrapper });
    
        expect(result.current).toMatchObject({ users: [], isFetching: true });
        
        // NEW WAY
        await waitFor(() => expect(result.current).toMatchObject({ users: [], isFetching: false }));
    
        // assert more...
      });
    });
  3. mirismaili commented on Oct 5, 2023

    @mirismaili

    This is my previous test on Apollo's useQuery using waitForNextUpdate:

    beforeAll(() => server.listen())
    afterEach(() => server.resetHandlers())
    afterAll(() => server.close())
    
    test('useQuery', async () => {
      const wrapper = ({ children }) => <ApolloProvider client={createMockApolloClient()}>{children}</ApolloProvider>
    
      const { result, waitForNextUpdate } = renderHook(
        () =>
          useQuery(GET_ORGANIZATION_PACKS_API, {
            variables: { orgUid },
            fetchPolicy: 'no-cache',
            notifyOnNetworkStatusChange: true,
          }),
        { wrapper },
      )
    
      let { data, loading, error, refetch, networkStatus } = result.current
      expect(loading).toBeTrue()
      expect(networkStatus).toBe(NetworkStatus.loading)
      expect(error).toBeUndefined()
      expect(data).toBeUndefined()
      expect(refetch).toBeFunction()
    
      await waitForNextUpdate({ timeout: 5000 })
      ;({ data, loading, error, refetch, networkStatus } = result.current)
    
      expect(error).toBeUndefined()
      expect(loading).toBeFalse()
      expect(networkStatus).toBe(NetworkStatus.ready)
      expect(data).toMatchSnapshot({}, 'GET_ORGANIZATION_PACKS_API')
      expect(refetch).toBeFunction()
      refetch().then()
    
      await waitForNextUpdate()
    
      const data0 = data
      ;({ data, loading, error, refetch, networkStatus } = result.current)
    
      expect(error).toBeUndefined()
      expect(loading).toBeTrue()
      expect(networkStatus).toBe(NetworkStatus.refetch)
      expect(data).toEqual(data0)
      expect(refetch).toBeFunction()
    
      await waitForNextUpdate()
      ;({ data, loading, error, refetch, networkStatus } = result.current)
    
      expect(error).toBeUndefined()
      expect(loading).toBeFalse()
      expect(networkStatus).toBe(NetworkStatus.ready)
      expect(data).toEqual(data0)
      expect(refetch).toBeFunction()
    })
    
    const GET_ORGANIZATION_PACKS_API = gql`...`

    It was working well.


    Now, how do I write it w/o waitForNextUpdate?

  4. MatanBobi commented on Oct 29, 2023

    @MatanBobi
    Member

    @mirismaili you can use waitFor for that.

    What about trying:

      waitFor(() => {
          expect(loading).toBeFalse()
      }, {timeout: 5000});
      waitFor(() => {
        expect(networkStatus).toBe(NetworkStatus.ready)
      }, {timeout: 5000});
    

    That should work.

  5. likeuwill commented on Nov 10, 2023

    @likeuwill

    To mock apollo queries and make it work in test

      it('should return the mocked data', async () => {
        const { result } = setUp()
    
        await waitFor(() => {
          expect(result.current.loading).toBeFalsy()
        })
    
        await waitFor(() => {
          expect(result.current.data).toStrictEqual({
            recentlyViewed: [recentlyViewed]
          })
        })
      })
    
  6. elyobo commented on Apr 2, 2024

    @elyobo

    waitFor + expect can break expect.assertions() expectations, as it may result in more assertions than expected due to retries

  7. githorse commented on Jul 6, 2024

    @githorse

    I don't see how waitFor is a replacement for waitForNextUpdate at all. waitFor doesn't say anything about how many re-renders happened before I got there. The argument here seems to be that the internals of the hook and its behavior are a black box, and I should only test the dom. Nice idea, but it's critical that I understand and control how many render cycles are triggered by my hook. Is that possible under the new paradigm?

    By way of example: I've just spent several days implementing some hooks based around Tanstack Query with selectors/transformations to ensure that consumers of the hook are only reloaded when their relevant slice of the underlying data is mutated (a la useContextSelector). It's a critical performance consideration for my app. How can I test that with this library?

  8. githorse commented on Jul 17, 2024

    @githorse

    Well, here's one way:

    import {
      queries,
      Queries,
      renderHook,
      RenderHookOptions,
      RenderHookResult
    } from '@testing-library/react'
    
    export interface MyRenderHookResult<Result, Props> extends RenderHookResult<Result, Props> {
      renders: MutableRefObject<Result[]>
    }
    
    export function myRenderHook<
      Result,
      Props,
      Q extends Queries = typeof queries,
      Container extends Element | DocumentFragment = HTMLElement,
      BaseElement extends Element | DocumentFragment = Container,
    >(
      render: (initialProps: Props) => Result,
      options?: RenderHookOptions<Props, Q, Container, BaseElement>,
    ): MyRenderHookResult<Result, Props> {
    
      const renders = {current: [] as Result[]}
    
      const _render = (props: Props) => {
        const returnValue = render(props)
        renders.current.push(returnValue)
        return returnValue
      }
    
      const result = renderHook(_render, options)
    
      return {
        ...result,
        renders
      }
    }
    
    ...
    
    test('it renders real nice', () => {
      const {result, renders} = myRenderHook(() => myHook())
      await waitFor(() => {
        expect(result.current).toEqual('ball python $49')
        expect(renders.current.length).toEqual(2)
      })
    })
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions