Skip to content
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

Random testing results when using useLazyQuery and useEffect #7388

Closed
Tracked by #8596
cjduncana opened this issue Nov 29, 2020 · 4 comments
Closed
Tracked by #8596

Random testing results when using useLazyQuery and useEffect #7388

cjduncana opened this issue Nov 29, 2020 · 4 comments
Assignees

Comments

@cjduncana
Copy link

Context:
I have a value that may exist when the page opens. If there's a value, I want to run the query, if the value changes, I want to rerun the query with the new value, and if there's no value, the code should not run the query. To solve this, I placed the trigger to the lazy query inside a useEffect that conditionally runs or reruns the query based on the existence or change of the value. When I went to test this component, that's when I discovered this bug. Using useQuery does not trigger this behavior.

Example of my code:

React.useEffect(() => {
  if (routerQuery) {
    searchUsers({ variables: { query: routerQuery } })
  }
}, [routerQuery, searchUsers])

Further down, I included a minimal, reproducible example using the provided React Apollo error template.

Intended outcome:
Running a test should have consistent results

Actual outcome:
The test will fail randomly. When the test fails, the useLazyQuery return these results:

// The query has not run
{ loading: false, networkStatus: 7, called: false, data: undefined }

// The query has run and is now loading
{
  variables: {},
  refetch: [Function],
  fetchMore: [Function],
  updateQuery: [Function],
  startPolling: [Function],
  stopPolling: [Function],
  subscribeToMore: [Function],
  data: undefined,
  loading: true,
  networkStatus: 1,
  error: undefined,
  called: true,
  client: ApolloClient {
    defaultOptions: {},
    resetStoreCallbacks: [],
    clearStoreCallbacks: [],
    link: MockLink {
      addTypename: false,
      mockedResponsesByKey: [Object],
      operation: [Object]
    },
    cache: InMemoryCache {
      getFragmentDoc: [Function],
      watches: [Set],
      typenameDocumentCache: Map {},
      makeVar: [Function: makeVar],
      txCount: 0,
      maybeBroadcastWatch: [Function],
      watchDep: [Function],
      config: [Object],
      addTypename: false,
      policies: [Policies],
      data: [Root],
      optimisticData: [Root],
      storeReader: [StoreReader],
      storeWriter: [StoreWriter],
      evict: [Function],
      modify: [Function],
      reset: [Function]
     },
    disableNetworkFetches: false,
    queryDeduplication: true,
    typeDefs: undefined,
    watchQuery: [Function: bound ],
    query: [Function: bound ],
    mutate: [Function: bound ],
    resetStore: [Function: bound ],
    reFetchObservableQueries: [Function: bound ],
    version: 'local',
    localState: LocalState { cache: [InMemoryCache], client: [Circular] },
    queryManager: QueryManager {
      mutationStore: [MutationStore],
      clientAwareness: [Object],
      queries: [Map],
      fetchCancelFns: [Map],
      transformCache: [WeakMap],
      queryIdCounter: 2,
      requestIdCounter: 2,
      mutationIdCounter: 1,
      inFlightLinkObservables: [Map],
      cache: [InMemoryCache],
      link: [MockLink],
      queryDeduplication: true,
      onBroadcast: [Function: onBroadcast],
      localState: [LocalState],
      ssrMode: false,
      assumeImmutableResults: false
    }
  }
}

// There's no successful result

When the test succeeds, the useLazyQuery return these results:

// The query has not run
{ loading: false, networkStatus: 7, called: false, data: undefined }

// The query has run and is now loading
{
  variables: {},
  refetch: [Function],
  fetchMore: [Function],
  updateQuery: [Function],
  startPolling: [Function],
  stopPolling: [Function],
  subscribeToMore: [Function],
  data: undefined,
  loading: true,
  networkStatus: 1,
  error: undefined,
  called: true,
  client: ApolloClient {
    defaultOptions: {},
    resetStoreCallbacks: [],
    clearStoreCallbacks: [],
    link: MockLink {
      addTypename: false,
      mockedResponsesByKey: [Object],
      operation: [Object]
    },
    cache: InMemoryCache {
      getFragmentDoc: [Function],
      watches: [Set],
      typenameDocumentCache: Map {},
      makeVar: [Function: makeVar],
      txCount: 0,
      maybeBroadcastWatch: [Function],
      watchDep: [Function],
      config: [Object],
      addTypename: false,
      policies: [Policies],
      data: [Root],
      optimisticData: [Root],
      storeReader: [StoreReader],
      storeWriter: [StoreWriter],
      evict: [Function],
      modify: [Function],
      reset: [Function]
    },
    disableNetworkFetches: false,
    queryDeduplication: true,
    typeDefs: undefined,
    watchQuery: [Function: bound ],
    query: [Function: bound ],
    mutate: [Function: bound ],
    resetStore: [Function: bound ],
    reFetchObservableQueries: [Function: bound ],
    version: 'local',
    localState: LocalState { cache: [InMemoryCache], client: [Circular] },
    queryManager: QueryManager {
      mutationStore: [MutationStore],
      clientAwareness: [Object],
      queries: [Map],
      fetchCancelFns: [Map],
      transformCache: [WeakMap],
      queryIdCounter: 2,
      requestIdCounter: 2,
      mutationIdCounter: 1,
      inFlightLinkObservables: [Map],
      cache: [InMemoryCache],
      link: [MockLink],
      queryDeduplication: true,
      onBroadcast: [Function: onBroadcast],
      localState: [LocalState],
      ssrMode: false,
      assumeImmutableResults: false
    }
  }
}

// The query has a successful result
{
  variables: {},
  refetch: [Function],
  fetchMore: [Function],
  updateQuery: [Function],
  startPolling: [Function],
  stopPolling: [Function],
  subscribeToMore: [Function],
  data: { people: [ [Object] ] },
  loading: false,
  networkStatus: 7,
  error: undefined,
  called: true,
  client: ApolloClient {
    defaultOptions: {},
    resetStoreCallbacks: [],
    clearStoreCallbacks: [],
    link: MockLink {
      addTypename: false,
      mockedResponsesByKey: [Object],
      operation: [Object]
    },
    cache: InMemoryCache {
      getFragmentDoc: [Function],
      watches: [Set],
      typenameDocumentCache: Map {},
      makeVar: [Function: makeVar],
      txCount: 0,
      maybeBroadcastWatch: [Function],
      watchDep: [Function],
      config: [Object],
      addTypename: false,
      policies: [Policies],
      data: [Root],
      optimisticData: [Root],
      storeReader: [StoreReader],
      storeWriter: [StoreWriter],
      evict: [Function],
      modify: [Function],
      reset: [Function]
    },
    disableNetworkFetches: false,
    queryDeduplication: true,
    typeDefs: undefined,
    watchQuery: [Function: bound ],
    query: [Function: bound ],
    mutate: [Function: bound ],
    resetStore: [Function: bound ],
    reFetchObservableQueries: [Function: bound ],
    version: 'local',
    localState: LocalState { cache: [InMemoryCache], client: [Circular] },
    queryManager: QueryManager {
      mutationStore: [MutationStore],
      clientAwareness: [Object],
      queries: [Map],
      fetchCancelFns: Map {},
      transformCache: [WeakMap],
      queryIdCounter: 2,
      requestIdCounter: 2,
      mutationIdCounter: 1,
      inFlightLinkObservables: Map {},
      cache: [InMemoryCache],
      link: [MockLink],
      queryDeduplication: true,
      onBroadcast: [Function: onBroadcast],
      localState: [LocalState],
      ssrMode: false,
      assumeImmutableResults: false
    }
  }
}

How to reproduce the issue:
Minimal, Reproducible Example

Use useLazyQuery and place its trigger inside of a useEffect, then test the query following the instructions in the Apollo docs.

Versions
System:
OS: macOS 10.15.7
Binaries:
Node: 12.19.0 - ~/.nvm/versions/node/v12.19.0/bin/node
npm: 6.14.8 - ~/.nvm/versions/node/v12.19.0/bin/npm
Browsers:
Safari: 14.0.1
npmPackages:
@apollo/client: ^3.2.5 => 3.2.5

@benjamn
Copy link
Member

benjamn commented Dec 1, 2020

@cjduncana Thanks for the reproduction! We'll take a look and get back to you soon, though don't hesitate to @-mention me if it's been more than a week.

@cjduncana
Copy link
Author

@benjamn Any news?

@merko30
Copy link

merko30 commented Mar 2, 2021

@benjamn Any updates on this ?

@brainkim brainkim assigned brainkim and unassigned benjamn Aug 13, 2021
@brainkim brainkim mentioned this issue Aug 13, 2021
14 tasks
@brainkim
Copy link
Contributor

brainkim commented Aug 16, 2021

Sorry about the delayed response. So the problem with the test is that you’re waiting for a promise based on setTimeout. There is no guarantee that the useEffect calls in your component will have been called by the time the test promise resolves. useEffect() runs according a new browser scheduling API which may fire slower than setTimeout. The react-testing-library docs seem to indicate that you can put the assertions directly in a waitFor callback, so something like this:

it('should render component', async () => {
  const mock = {
    request: { query: ALL_PEOPLE },
    result: {
      data: { people: [{ id: 1, name: 'John Doe' }] },
    },
  };

  const component = TestRenderer.create(
    <MockedProvider mocks={[mock]} addTypename={false}>
      <App />
    </MockedProvider>,
  );

  await TestRenderer.waitFor(() => {
    const p = component.root.findByType('li');
    expect(p.props.children).toEqual('John Doe');
  });
});

This doesn’t seem like something we can fix in Apollo Client itself, so I’m closing the issue. Please ping and yell at me if this is not the case. 😇

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants