Repository: apollographql/apollo-client

Problem statement:
`useMutation`/`useLazyQuery` execution functions always requires full variables when using TypedDocumentNode
### Issue Description

The documentation for hooks like [`useMutation`](https://www.apollographql.com/docs/react/data/mutations#option-precedence) and [`useLazyQuery`](https://www.apollographql.com/docs/react/data/queries#manual-execution-with-uselazyquery) mention that there are two distinct ways to provide variables to the actual request: (1) as an option to the hook itself and (2) as an option to the query function returned by the hook. These allow variable merging such that the query function used at the call-site can override any values provided in the hook, and the docs linked above advertise this as a good way to provide defaults.

The issue I'm running into is using this with Typescript and specifically `TypedDocumentNode`s that strongly type the variables to the operation, because the hooks always require the fully formed Variables rather than something than might support the merging / default structure you'd expect. Some possible solutions might be something straightforward like allowing a `Partial` at both sites or something more involved that makes any fields provided in the hook optional in the query function.

Check the quick sketch below as well as the provided Codesandbox to see this issue in more detail:

Imagine we'd like to use the following mutation
```gql
mutation AddPerson($name: String, $title: String) {
  addPerson(name: $name, title: $title) {
    node {
      id
      name
      title
    }
  }
}
```

Usage of `useMutation` might look something like this, which creates errors in both the hook declaration and the execution usage, despite functionally working properly if you erase those errors with `as any`.
```ts
const [addEngineer] = useMutation(TYPED_DOCUMENT_NODE, {
  // ❌ Property 'name' is missing in type '{ title: string; }' but required in type '{ name: string; title: string; }'.
  variables: {
    title: 'Engineer'
  }
});

// ❌ Property 'title' is missing in type '{ name: string; }' but required in type '{ name: string; title: string; }'.
useEffect(() => addEngineer({ variables: { name: 'Alvin' } }), []);
```

The same sort of issue occurs for `useLazyQuery` instances where the provided operation takes variables.

### Link to Reproduction

https://codesandbox.io/p/devbox/determined-elbakyan-vrsjcs?workspaceId=59bd876a-c3ad-4ffd-a783-ffc36a11e7d9

### Reproduction Steps


#### Try it out
1. Run the [Codesandbox](https://codesandbox.io/p/devbox/determined-elbakyan-vrsjcs?workspaceId=59bd876a-c3ad-4ffd-a783-ffc36a11e7d9)
2. Type in any name and click "Add engineer"
3. Observe that the name provided is correctly added to the list and notice that they're annotated as an Engineer

#### To the code
1. Jump into the code and find two `as any` typecasts around `useMutation` and the usage of the execution function in `src/index.tsx`
2. Remove both typecasts

Observe type errors despite the working functionality described in the first section above

### `@apollo/client` version

3.11.8


Hints:
Hey @maciesielka 👋 

Good catch! FYI the team is away at a conference this week so we won't be able to look at this until at least next week. Thanks for raising the issue!
Hi @jerelmiller, can I have a try at this if possible?
@naman1608 absolutely!
@jerelmiller, I have a doubt if you could take a look? Should `MutationFunctionOptions` type have a mutation key? The mutate function shouldn't be able to take a mutation right? It's also not mentioned in the docs - https://www.apollographql.com/docs/react/data/mutations#options
Hi @maciesielka and @naman1608, 
I'm sorry that I do have to go back on this a bit:

As these "double options", especially the merging behaviour between them, are very confusing, we're actually considering to deprecate or even remove the `variables` option on the `useMutation`/`useLazyQuery` hooks and only allow variables to be passed into the respective `execute` function.

In that context, I'm honestly very reluctant towards any type changes here now - we want to start work on 4.0 very soon, and it would be very confusing for users if we went back-and-forth on this so quickly.
Okay that makes sense if improving the typing experience here might encourage usage of functionality that's on its way out. I'll try and soft-deprecate usage of the variable merging in my projects until y'all get around to officially deprecating it. 
Yup makes sense, thanks @phryneas!!

Patch:
diff --git a/src/react/hooks/useLazyQuery.ts b/src/react/hooks/useLazyQuery.ts
--- a/src/react/hooks/useLazyQuery.ts
+++ b/src/react/hooks/useLazyQuery.ts
@@ -1,37 +1,165 @@
 import type { TypedDocumentNode } from "@graphql-typed-document-node/core";
+import { equal } from "@wry/equality";
 import type { DocumentNode } from "graphql";
 import * as React from "rehackt";
 
 import type {
   ApolloClient,
+  ApolloError,
+  ApolloQueryResult,
+  DefaultContext,
+  ErrorPolicy,
+  FetchMoreQueryOptions,
+  MaybeMasked,
   ObservableQuery,
   OperationVariables,
+  RefetchWritePolicy,
+  SubscribeToMoreFunction,
+  Unmasked,
+  UpdateQueryMapFn,
+  WatchQueryFetchPolicy,
   WatchQueryOptions,
 } from "@apollo/client/core";
+import { NetworkStatus } from "@apollo/client/core";
+import type { NoInfer } from "@apollo/client/react";
 import type {
-  LazyQueryHookExecOptions,
-  LazyQueryHookOptions,
-  LazyQueryResultTuple,
-  NoInfer,
-  QueryHookOptions,
+  ObservableQueryFields,
+  VariablesOption,
 } from "@apollo/client/react";
-import { mergeOptions } from "@apollo/client/utilities";
+import type { OnlyRequiredProperties } from "@apollo/client/utilities";
+import { maybeDeepFreeze } from "@apollo/client/utilities";
+import { invariant } from "@apollo/client/utilities/invariant";
 
+import type { NextFetchPolicyContext } from "../../core/watchQueryOptions.js";
+
+import { useRenderGuard } from "./internal/index.js";
+import { useDeepMemo } from "./internal/useDeepMemo.js";
 import { useIsomorphicLayoutEffect } from "./internal/useIsomorphicLayoutEffect.js";
-import type { InternalResult } from "./useQuery.js";
-import {
-  createMakeWatchQueryOptions,
-  getDefaultFetchPolicy,
-  getObsQueryOptions,
-  toQueryResult,
-  useQueryInternals,
-} from "./useQuery.js";
+import { useApolloClient } from "./useApolloClient.js";
+import { useSyncExternalStore } from "./useSyncExternalStore.js";
+
+export interface LazyQueryHookOptions<
+  TData = any,
+  TVariables extends OperationVariables = OperationVariables,
+> {
+  /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#fetchPolicy:member} */
+  fetchPolicy?: WatchQueryFetchPolicy;
+
+  /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#nextFetchPolicy:member} */
+  nextFetchPolicy?:
+    | WatchQueryFetchPolicy
+    | ((
+        this: WatchQueryOptions<TVariables, TData>,
+        currentFetchPolicy: WatchQueryFetchPolicy,
+        context: NextFetchPolicyContext<TData, TVariables>
+      ) => WatchQueryFetchPolicy);
+
+  /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#refetchWritePolicy:member} */
+  refetchWritePolicy?: RefetchWritePolicy;
+
+  /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#errorPolicy:member} */
+  errorPolicy?: ErrorPolicy;
+
+  /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#pollInterval:member} */
+  pollInterval?: number;
+
+  /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#notifyOnNetworkStatusChange:member} */
+  notifyOnNetworkStatusChange?: boolean;
+
+  /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#returnPartialData:member} */
+  returnPartialData?: boolean;
+
+  /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#canonizeResults:member} */
+  canonizeResults?: boolean;
+
+  /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#skipPollAttempt:member} */
+  skipPollAttempt?: () => boolean;
+
+  /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#client:member} */
+  client?: ApolloClient<any>;
+
+  /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#context:member} */
+  context?: DefaultContext;
+}
+
+export type LazyQueryHookExecOptions<
+  TVariables extends OperationVariables = OperationVariables,
+> = {
+  /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#context:member} */
+  context?: DefaultContext;
+} & VariablesOption<TVariables>;
+
+export interface LazyQueryResult<TData, TVariables extends OperationVariables> {
+  /** {@inheritDoc @apollo/client!QueryResultDocumentation#startPolling:member} */
+  startPolling: (pollInterval: number) => void;
+  /** {@inheritDoc @apollo/client!QueryResultDocumentation#stopPolling:member} */
+  stopPolling: () => void;
+  /** {@inheritDoc @apollo/client!QueryResultDocumentation#subscribeToMore:member} */
+  subscribeToMore: SubscribeToMoreFunction<TData, TVariables>;
+  /** {@inheritDoc @apollo/client!QueryResultDocumentation#updateQuery:member} */
+  updateQuery: (mapFn: UpdateQueryMapFn<TData, TVariables>) => void;
+  /** {@inheritDoc @apollo/client!QueryResultDocumentation#refetch:member} */
+  refetch: (
+    variables?: Partial<TVariables>
+  ) => Promise<ApolloQueryResult<MaybeMasked<TData>>>;
+  /** {@inheritDoc @apollo/client!QueryResultDocumentation#variables:member} */
+  variables: TVariables | undefined;
+  /** {@inheritDoc @apollo/client!QueryResultDocumentation#fetchMore:member} */
+  fetchMore: <
+    TFetchData = TData,
+    TFetchVars extends OperationVariables = TVariables,
+  >(
+    fetchMoreOptions: FetchMoreQueryOptions<TFetchVars, TFetchData> & {
+      updateQuery?: (
+        previousQueryResult: Unmasked<TData>,
+        options: {
+          fetchMoreResult: Unmasked<TFetchData>;
+          variables: TFetchVars;
+        }
+      ) => Unmasked<TData>;
+    }
+  ) => Promise<ApolloQueryResult<MaybeMasked<TFetchData>>>;
+  /** {@inheritDoc @apollo/client!QueryResultDocumentation#client:member} */
+  client: ApolloClient<any>;
+  /** {@inheritDoc @apollo/client!QueryResultDocumentation#observable:member} */
+  observable: ObservableQuery<TData, TVariables>;
+  /** {@inheritDoc @apollo/client!QueryResultDocumentation#data:member} */
+  data: MaybeMasked<TData> | undefined;
+  /** {@inheritDoc @apollo/client!QueryResultDocumentation#previousData:member} */
+  previousData?: MaybeMasked<TData>;
+  /** {@inheritDoc @apollo/client!QueryResultDocumentation#error:member} */
+  error?: ApolloError;
+  /** {@inheritDoc @apollo/client!QueryResultDocumentation#loading:member} */
+  loading: boolean;
+  /** {@inheritDoc @apollo/client!QueryResultDocumentation#networkStatus:member} */
+  networkStatus: NetworkStatus;
+  /** {@inheritDoc @apollo/client!QueryResultDocumentation#called:member} */
+  called: boolean;
+}
+
+export type LazyQueryResultTuple<
+  TData,
+  TVariables extends OperationVariables,
+> = [
+  execute: LazyQueryExecFunction<TData, TVariables>,
+  result: LazyQueryResult<TData, TVariables>,
+];
+
+export type LazyQueryExecFunction<
+  TData,
+  TVariables extends OperationVariables,
+> = (
+  ...args: [TVariables] extends [never] ?
+    [options?: LazyQueryHookExecOptions<TVariables>]
+  : Record<string, never> extends OnlyRequiredProperties<TVariables> ?
+    [options?: LazyQueryHookExecOptions<TVariables>]
+  : [options: LazyQueryHookExecOptions<TVariables>]
+) => Promise<ApolloQueryResult<TData>>;
 
 // The following methods, when called will execute the query, regardless of
 // whether the useLazyQuery execute function was called before.
 const EAGER_METHODS = [
   "refetch",
-  "reobserve",
   "fetchMore",
   "updateQuery",
   "startPolling",
@@ -81,112 +209,153 @@ export function useLazyQuery<
   query: DocumentNode | TypedDocumentNode<TData, TVariables>,
   options?: LazyQueryHookOptions<NoInfer<TData>, NoInfer<TVariables>>
 ): LazyQueryResultTuple<TData, TVariables> {
-  const execOptionsRef =
-    React.useRef<Partial<LazyQueryHookExecOptions<TData, TVariables>>>(void 0);
-  const optionsRef =
-    React.useRef<LazyQueryHookOptions<TData, TVariables>>(void 0);
-  const queryRef = React.useRef<
-    DocumentNode | TypedDocumentNode<TData, TVariables>
-  >(void 0);
-  const merged = mergeOptions(options, execOptionsRef.current || {});
-  const document = merged?.query ?? query;
-
-  // Use refs to track options and the used query to ensure the `execute`
-  // function remains referentially stable between renders.
-  optionsRef.current = options;
-  queryRef.current = document;
-
-  const queryHookOptions = {
-    ...merged,
-    skip: !execOptionsRef.current,
-  };
-  const {
-    obsQueryFields,
-    result: useQueryResult,
-    client,
-    resultData,
-    observable,
-    onQueryExecuted,
-  } = useQueryInternals(document, queryHookOptions);
+  const client = useApolloClient(options?.client);
+  const previousDataRef = React.useRef<TData>(undefined);
+  const resultRef = React.useRef<ApolloQueryResult<TData>>(undefined);
+  const stableOptions = useDeepMemo(() => options, [options]);
+  const calledDuringRender = useRenderGuard();
+
+  function createObservable() {
+    return client.watchQuery({
+      ...options,
+      query,
+      initialFetchPolicy: options?.fetchPolicy,
+      fetchPolicy: "standby",
+    });
+  }
+
+  const [currentClient, setCurrentClient] = React.useState(client);
+  const [observable, setObservable] = React.useState(createObservable);
+
+  if (currentClient !== client) {
+    setCurrentClient(client);
+    setObservable(createObservable());
+  }
+
+  // TODO: Revisit after we have RxJS in place. We should be able to use
+  // observable.getCurrentResult() (or equivalent) to get these values which
+  // will hopefully alleviate the need for us to use refs to track these values.
+  const updateResult = React.useCallback(
+    (result: ApolloQueryResult<TData>, forceUpdate: () => void) => {
+      const previousData = resultRef.current?.data;
+
+      if (previousData && !equal(previousData, result.data)) {
+        // eslint-disable-next-line react-compiler/react-compiler
+        previousDataRef.current = previousData;
+      }
+
+      resultRef.current = result;
+
+      forceUpdate();
+    },
+    []
+  );
+
+  const observableResult = useSyncExternalStore(
+    React.useCallback(
+      (forceUpdate) => {
+        const subscription = observable.subscribe((result) => {
+          if (!equal(resultRef.current, result)) {
+            updateResult(result, forceUpdate);
+          }
+        });
 
-  const initialFetchPolicy =
-    observable.options.initialFetchPolicy ||
-    getDefaultFetchPolicy(
-      queryHookOptions.defaultOptions,
-      client.defaultOptions
-    );
+        return () => {
+          subscription.unsubscribe();
+        };
+      },
+      [observable, updateResult]
+    ),
+    () => resultRef.current || initialResult,
+    () => initialResult
+  );
 
-  const forceUpdateState = React.useReducer((tick) => tick + 1, 0)[1];
+  const [, forceUpdateState] = React.useReducer((tick) => tick + 1, 0);
   // We use useMemo here to make sure the eager methods have a stable identity.
   const eagerMethods = React.useMemo(() => {
     const eagerMethods: Record<string, any> = {};
     for (const key of EAGER_METHODS) {
-      const method = obsQueryFields[key];
       eagerMethods[key] = function () {
-        if (!execOptionsRef.current) {
-          execOptionsRef.current = {};
-          // Only the first time populating execOptionsRef.current matters here.
-          forceUpdateState();
-        }
-        // @ts-expect-error this is just too generic to type
-        return method.apply(this, arguments);
+        invariant(
+          resultRef.current,
+          "useLazyQuery: '%s' cannot be called before executing the query.",
+          key
+        );
+
+        // @ts-expect-error this is just to generic to type
+        return observable[key].apply(observable, arguments);
       };
     }
 
-    return eagerMethods as typeof obsQueryFields;
-  }, [forceUpdateState, obsQueryFields]);
+    return eagerMethods as Pick<
+      ObservableQueryFields<TData, TVariables>,
+      (typeof EAGER_METHODS)[number]
+    >;
+  }, [observable]);
 
-  const called = !!execOptionsRef.current;
-  const result = React.useMemo(
-    () => ({
-      ...useQueryResult,
-      ...eagerMethods,
-      called,
-    }),
-    [useQueryResult, eagerMethods, called]
-  );
+  React.useEffect(() => {
+    const updatedOptions: Partial<WatchQueryOptions<TVariables, TData>> = {
+      query,
+      errorPolicy: stableOptions?.errorPolicy,
+      context: stableOptions?.context,
+      refetchWritePolicy: stableOptions?.refetchWritePolicy,
+      returnPartialData: stableOptions?.returnPartialData,
+      notifyOnNetworkStatusChange: stableOptions?.notifyOnNetworkStatusChange,
+      nextFetchPolicy: options?.nextFetchPolicy,
+      skipPollAttempt: options?.skipPollAttempt,
+    };
 
-  const execute = React.useCallback<LazyQueryResultTuple<TData, TVariables>[0]>(
-    (executeOptions) => {
-      execOptionsRef.current =
-        executeOptions ?
-          {
-            ...executeOptions,
-            fetchPolicy: executeOptions.fetchPolicy || initialFetchPolicy,
-          }
-        : {
-            fetchPolicy: initialFetchPolicy,
-          };
-
-      const options = mergeOptions(optionsRef.current, {
-        query: queryRef.current,
-        ...execOptionsRef.current,
-      });
-
-      const promise = executeQuery(
-        resultData,
-        observable,
-        client,
-        document,
-        { ...options, skip: false },
-        onQueryExecuted
-      ).then((queryResult) => Object.assign(queryResult, eagerMethods));
-
-      // Because the return value of `useLazyQuery` is usually floated, we need
-      // to catch the promise to prevent unhandled rejections.
-      promise.catch(() => {});
+    // Wait to apply the changed fetch policy until after the execute
+    // function has been called. The execute function will handle setting the
+    // the fetch policy away from standby for us when called for the first time.
+    if (
+      observable.options.fetchPolicy !== "standby" &&
+      stableOptions?.fetchPolicy
+    ) {
+      updatedOptions.fetchPolicy = stableOptions?.fetchPolicy;
+    }
+
+    observable.silentSetOptions(updatedOptions);
+  }, [
+    query,
+    observable,
+    stableOptions,
+    // Ensure inline functions don't suffer from stale closures by checking for
+    // these deps separately. @wry/equality doesn't compare function identity
+    // so `stableOptions` isn't updated when using inline functions.
+    options?.nextFetchPolicy,
+    options?.skipPollAttempt,
+  ]);
+
+  const execute: LazyQueryExecFunction<TData, TVariables> = React.useCallback(
+    (...args) => {
+      invariant(
+        !calledDuringRender(),
+        "useLazyQuery: 'execute' should not be called during render. To start a query during render, use the 'useQuery' hook."
+      );
+
+      const [executeOptions] = args;
+
+      const options: Partial<WatchQueryOptions<TVariables, TData>> = {
+        ...executeOptions,
+        // TODO: Figure out a better way to reset variables back to empty
+        variables: (executeOptions?.variables ?? {}) as TVariables,
+      };
+
+      if (observable.options.fetchPolicy === "standby") {
+        options.fetchPolicy = observable.options.initialFetchPolicy;
+      }
+
+      const promise = observable.setOptions(options);
+
+      // TODO: This should be fixed in core
+      if (!resultRef.current && stableOptions?.notifyOnNetworkStatusChange) {
+        updateResult(observable.getCurrentResult(), forceUpdateState);
+      }
 
       return promise;
     },
-    [
-      client,
-      document,
-      eagerMethods,
-      initialFetchPolicy,
-      observable,
-      resultData,
-      onQueryExecuted,
-    ]
+    [observable, stableOptions, updateResult, calledDuringRender]
   );
 
   const executeRef = React.useRef(execute);
@@ -198,41 +367,26 @@ export function useLazyQuery<
     (...args) => executeRef.current(...args),
     []
   );
-  return [stableExecute, result];
-}
-
-function executeQuery<TData, TVariables extends OperationVariables>(
-  resultData: InternalResult<TData, TVariables>,
-  observable: ObservableQuery<TData, TVariables>,
-  client: ApolloClient<object>,
-  currentQuery: DocumentNode,
-  options: QueryHookOptions<TData, TVariables> & {
-    query?: DocumentNode;
-  },
-  onQueryExecuted: (options: WatchQueryOptions<TVariables, TData>) => void
-) {
-  const query = options.query || currentQuery;
-  const watchQueryOptions = createMakeWatchQueryOptions(
-    client,
-    query,
-    options,
-    false
-  )(observable);
 
-  const promise = observable.reobserve(
-    getObsQueryOptions(observable, client, options, watchQueryOptions)
-  );
-  onQueryExecuted(watchQueryOptions);
-
-  return promise.then(
-    (result) =>
-      toQueryResult(result, resultData.previousData, observable, client),
-    () =>
-      toQueryResult(
-        observable.getCurrentResult(),
-        resultData.previousData,
-        observable,
-        client
-      )
+  const result = React.useMemo(
+    () => ({
+      ...eagerMethods,
+      ...observableResult,
+      client,
+      previousData: previousDataRef.current,
+      variables: observable.variables,
+      observable,
+      called: !!resultRef.current,
+    }),
+    [client, observableResult, eagerMethods, observable]
   );
+
+  return [stableExecute, result];
 }
+
+const initialResult: ApolloQueryResult<any> = maybeDeepFreeze({
+  data: undefined,
+  loading: false,
+  networkStatus: NetworkStatus.ready,
+  partial: true,
+});
diff --git a/src/react/hooks/useQuery.ts b/src/react/hooks/useQuery.ts
--- a/src/react/hooks/useQuery.ts
+++ b/src/react/hooks/useQuery.ts
@@ -62,7 +62,7 @@ interface ObsQueryWithMeta<TData, TVariables extends OperationVariables>
   [lastWatchOptions]?: WatchQueryOptions<TVariables, TData>;
 }
 
-export interface InternalResult<TData, TVariables extends OperationVariables> {
+interface InternalResult<TData, TVariables extends OperationVariables> {
   // These members are populated by getCurrentResult and setResult, and it's
   // okay/normal for them to be initially undefined.
   current?: undefined | InternalQueryResult<TData, TVariables>;
@@ -132,7 +132,12 @@ function useQuery_<
   query: DocumentNode | TypedDocumentNode<TData, TVariables>,
   options: QueryHookOptions<NoInfer<TData>, NoInfer<TVariables>>
 ) {
-  const { result, obsQueryFields } = useQueryInternals(query, options);
+  const result = useQueryInternals(query, options);
+  const obsQueryFields = React.useMemo(
+    () => bindObservableMethods(result.observable),
+    [result.observable]
+  );
+
   return React.useMemo(
     () => ({ ...result, ...obsQueryFields }),
     [result, obsQueryFields]
@@ -177,33 +182,6 @@ function useInternalState<
   let [internalState, updateInternalState] =
     React.useState(createInternalState);
 
-  /**
-   * Used by `useLazyQuery` when a new query is executed.
-   * We keep this logic here since it needs to update things in unsafe
-   * ways and here we at least can keep track of that in a single place.
-   */
-  function onQueryExecuted(
-    watchQueryOptions: WatchQueryOptions<TVariables, TData>
-  ) {
-    // this needs to be set to prevent an immediate `resubscribe` in the
-    // next rerender of the `useQuery` internals
-    Object.assign(internalState.observable, {
-      [lastWatchOptions]: watchQueryOptions,
-    });
-    const resultData = internalState.resultData;
-    updateInternalState({
-      ...internalState,
-      // might be a different query
-      query: watchQueryOptions.query,
-      resultData: Object.assign(resultData, {
-        // We need to modify the previous `resultData` object as we rely on the
-        // object reference in other places
-        previousData: resultData.current?.data || resultData.previousData,
-        current: undefined,
-      }),
-    });
-  }
-
   if (client !== internalState.client || query !== internalState.query) {
     // If the client or query have changed, we need to create a new InternalState.
     // This will trigger a re-render with the new state, but it will also continue
@@ -213,13 +191,13 @@ function useInternalState<
     // triggered with the new state.
     const newInternalState = createInternalState(internalState);
     updateInternalState(newInternalState);
-    return [newInternalState, onQueryExecuted] as const;
+    return newInternalState;
   }
 
-  return [internalState, onQueryExecuted] as const;
+  return internalState;
 }
 
-export function useQueryInternals<
+function useQueryInternals<
   TData = any,
   TVariables extends OperationVariables = OperationVariables,
 >(
@@ -240,7 +218,7 @@ export function useQueryInternals<
     isSyncSSR
   );
 
-  const [{ observable, resultData }, onQueryExecuted] = useInternalState(
+  const { observable, resultData } = useInternalState(
     client,
     query,
     options,
@@ -259,11 +237,6 @@ export function useQueryInternals<
     watchQueryOptions
   );
 
-  const obsQueryFields = React.useMemo(
-    () => bindObservableMethods(observable),
-    [observable]
-  );
-
   useRegisterSSRObservable(observable, renderPromises, ssrAllowed);
 
   const result = useObservableSubscriptionResult<TData, TVariables>(
@@ -276,14 +249,7 @@ export function useQueryInternals<
     isSyncSSR
   );
 
-  return {
-    result,
-    obsQueryFields,
-    observable,
-    resultData,
-    client,
-    onQueryExecuted,
-  };
+  return result;
 }
 
 function useObservableSubscriptionResult<
@@ -450,7 +416,7 @@ function useResubscribeIfNecessary<
  * This is two-step curried because we want to reuse the `make` function,
  * but the `observable` might differ between calls to `make`.
  */
-export function createMakeWatchQueryOptions<
+function createMakeWatchQueryOptions<
   TData = any,
   TVariables extends OperationVariables = OperationVariables,
 >(
@@ -508,10 +474,7 @@ export function createMakeWatchQueryOptions<
   };
 }
 
-export function getObsQueryOptions<
-  TData,
-  TVariables extends OperationVariables,
->(
+function getObsQueryOptions<TData, TVariables extends OperationVariables>(
   observable: ObservableQuery<TData, TVariables> | undefined,
   client: ApolloClient<object>,
   queryHookOptions: QueryHookOptions<TData, TVariables>,
@@ -584,10 +547,7 @@ function getCurrentResult<TData, TVariables extends OperationVariables>(
   return resultData.current!;
 }
 
-export function getDefaultFetchPolicy<
-  TData,
-  TVariables extends OperationVariables,
->(
+function getDefaultFetchPolicy<TData, TVariables extends OperationVariables>(
   queryHookDefaultOptions?: Partial<WatchQueryOptions<TVariables, TData>>,
   clientDefaultOptions?: DefaultOptions
 ): WatchQueryFetchPolicy {
@@ -598,7 +558,7 @@ export function getDefaultFetchPolicy<
   );
 }
 
-export function toQueryResult<TData, TVariables extends OperationVariables>(
+function toQueryResult<TData, TVariables extends OperationVariables>(
   result: ApolloQueryResult<MaybeMasked<TData>>,
   previousData: MaybeMasked<TData> | undefined,
   observable: ObservableQuery<TData, TVariables>,
diff --git a/src/react/types/types.ts b/src/react/types/types.ts
--- a/src/react/types/types.ts
+++ b/src/react/types/types.ts
@@ -151,20 +151,6 @@ export interface QueryHookOptions<
   TVariables extends OperationVariables = OperationVariables,
 > extends QueryFunctionOptions<TData, TVariables> {}
 
-export interface LazyQueryHookOptions<
-  TData = any,
-  TVariables extends OperationVariables = OperationVariables,
-> extends BaseQueryOptions<TVariables, TData> {
-  /** @internal */
-  defaultOptions?: Partial<WatchQueryOptions<TVariables, TData>>;
-}
-export interface LazyQueryHookExecOptions<
-  TData = any,
-  TVariables extends OperationVariables = OperationVariables,
-> extends LazyQueryHookOptions<TData, TVariables> {
-  query?: DocumentNode | TypedDocumentNode<TData, TVariables>;
-}
-
 export type SuspenseQueryHookFetchPolicy = Extract<
   WatchQueryFetchPolicy,
   "cache-first" | "network-only" | "no-cache" | "cache-and-network"
@@ -264,47 +250,6 @@ export interface LoadableQueryHookOptions {
   returnPartialData?: boolean;
 }
 
-/**
- * @deprecated This type will be removed in the next major version of Apollo Client
- */
-export interface QueryLazyOptions<TVariables> {
-  /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#variables:member} */
-  variables?: TVariables;
-  /** {@inheritDoc @apollo/client!QueryOptionsDocumentation#context:member} */
-  context?: DefaultContext;
-}
-
-/**
- * @deprecated This type will be removed in the next major version of Apollo Client
- */
-export type LazyQueryResult<
-  TData,
-  TVariables extends OperationVariables,
-> = QueryResult<TData, TVariables>;
-
-/**
- * @deprecated This type will be removed in the next major version of Apollo Client
- */
-export type QueryTuple<
-  TData,
-  TVariables extends OperationVariables,
-> = LazyQueryResultTuple<TData, TVariables>;
-
-export type LazyQueryExecFunction<
-  TData,
-  TVariables extends OperationVariables,
-> = (
-  options?: Partial<LazyQueryHookExecOptions<TData, TVariables>>
-) => Promise<QueryResult<TData, TVariables>>;
-
-export type LazyQueryResultTuple<
-  TData,
-  TVariables extends OperationVariables,
-> = [
-  execute: LazyQueryExecFunction<TData, TVariables>,
-  result: QueryResult<TData, TVariables>,
-];
-
 /* Mutation types */
 
 export type RefetchQueriesFunction = (


Test patch:
diff --git a/src/react/hooks/__tests__/useLazyQuery.test.tsx b/src/react/hooks/__tests__/useLazyQuery.test.tsx
--- a/src/react/hooks/__tests__/useLazyQuery.test.tsx
+++ b/src/react/hooks/__tests__/useLazyQuery.test.tsx
@@ -1,22 +1,27 @@
-import { act, renderHook, waitFor } from "@testing-library/react";
+import { act, renderHook, screen, waitFor } from "@testing-library/react";
 import {
   disableActEnvironment,
   renderHookToSnapshotStream,
 } from "@testing-library/react-render-stream";
+import { userEvent } from "@testing-library/user-event";
 import { expectTypeOf } from "expect-type";
 import { GraphQLError } from "graphql";
 import { gql } from "graphql-tag";
 import React from "react";
+import { ErrorBoundary } from "react-error-boundary";
 import { Observable } from "rxjs";
 
 import {
   ApolloClient,
   ApolloError,
   ApolloLink,
+  ApolloQueryResult,
   ErrorPolicy,
   InMemoryCache,
   NetworkStatus,
+  RefetchWritePolicy,
   TypedDocumentNode,
+  WatchQueryFetchPolicy,
 } from "@apollo/client/core";
 import { Masked, MaskedDocumentNode, Unmasked } from "@apollo/client/masking";
 import { ApolloProvider } from "@apollo/client/react";
@@ -24,19 +29,21 @@ import {
   MockLink,
   mockSingleLink,
   MockSubscriptionLink,
-  tick,
   wait,
 } from "@apollo/client/testing";
 import { MockedProvider } from "@apollo/client/testing/react";
 import { DeepPartial } from "@apollo/client/utilities";
 import { InvariantError } from "@apollo/client/utilities/invariant";
 
-import { QueryResult } from "../../types/types.js";
+import {
+  renderAsync,
+  setupSimpleCase,
+  setupVariablesCase,
+  spyOnConsole,
+  VariablesCaseVariables,
+} from "../../../testing/internal/index.js";
 import { useLazyQuery } from "../useLazyQuery.js";
 
-const IS_REACT_17 = React.version.startsWith("17");
-const IS_REACT_18 = React.version.startsWith("18");
-
 describe("useLazyQuery Hook", () => {
   const helloQuery: TypedDocumentNode<{
     hello: string;
@@ -66,9 +73,8 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
@@ -78,26 +84,19 @@ describe("useLazyQuery Hook", () => {
     }
 
     const [execute] = getCurrentSnapshot();
+    const result = await execute();
 
-    setTimeout(() => execute());
-
-    {
-      const [, result] = await takeSnapshot();
-
-      expect(result).toEqualQueryResult({
-        data: undefined,
-        called: true,
-        loading: true,
-        networkStatus: NetworkStatus.loading,
-        previousData: undefined,
-        variables: {},
-      });
-    }
+    expect(result).toEqualApolloQueryResult({
+      data: { hello: "world" },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { hello: "world" },
         called: true,
         loading: false,
@@ -106,64 +105,11 @@ describe("useLazyQuery Hook", () => {
         variables: {},
       });
     }
-  });
-
-  it("should set `called` to false by default", async () => {
-    using _disabledAct = disableActEnvironment();
-    const { takeSnapshot } = await renderHookToSnapshotStream(
-      () => useLazyQuery(helloQuery),
-      {
-        wrapper: ({ children }) => (
-          <MockedProvider mocks={[]}>{children}</MockedProvider>
-        ),
-      }
-    );
-
-    const [, { called }] = await takeSnapshot();
-
-    expect(called).toBe(false);
-  });
-
-  it("should set `called` to true after calling the lazy execute function", async () => {
-    const mocks = [
-      {
-        request: { query: helloQuery },
-        result: { data: { hello: "world" } },
-        delay: 20,
-      },
-    ];
-
-    using _disabledAct = disableActEnvironment();
-    const { takeSnapshot, getCurrentSnapshot } =
-      await renderHookToSnapshotStream(() => useLazyQuery(helloQuery), {
-        wrapper: ({ children }) => (
-          <MockedProvider mocks={mocks}>{children}</MockedProvider>
-        ),
-      });
-
-    {
-      const [, { loading, called }] = await takeSnapshot();
-      expect(loading).toBe(false);
-      expect(called).toBe(false);
-    }
-
-    const execute = getCurrentSnapshot()[0];
-    setTimeout(() => execute());
-
-    {
-      const [, { loading, called }] = await takeSnapshot();
-      expect(loading).toBe(true);
-      expect(called).toBe(true);
-    }
 
-    {
-      const [, { loading, called }] = await takeSnapshot();
-      expect(loading).toBe(false);
-      expect(called).toBe(true);
-    }
+    await expect(takeSnapshot).not.toRerender();
   });
 
-  it("should use variables defined in hook options (if any), when running the lazy execution function", async () => {
+  it("should use variables passed to execute function when running the lazy execution function", async () => {
     const query = gql`
       query ($id: number) {
         hello(id: $id)
@@ -180,51 +126,39 @@ describe("useLazyQuery Hook", () => {
 
     using _disabledAct = disableActEnvironment();
     const { takeSnapshot, getCurrentSnapshot } =
-      await renderHookToSnapshotStream(
-        () =>
-          useLazyQuery(query, {
-            variables: { id: 1 },
-          }),
-        {
-          wrapper: ({ children }) => (
-            <MockedProvider mocks={mocks}>{children}</MockedProvider>
-          ),
-        }
-      );
+      await renderHookToSnapshotStream(() => useLazyQuery(query), {
+        wrapper: ({ children }) => (
+          <MockedProvider mocks={mocks}>{children}</MockedProvider>
+        ),
+      });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
         previousData: undefined,
-        variables: { id: 1 },
+        variables: {},
       });
     }
 
-    const execute = getCurrentSnapshot()[0];
-    setTimeout(() => execute());
+    const [execute] = getCurrentSnapshot();
+    const result = await execute({ variables: { id: 1 } });
 
-    {
-      const [, result] = await takeSnapshot();
+    expect(result).toEqualApolloQueryResult({
+      data: { hello: "world 1" },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
 
-      expect(result).toEqualQueryResult({
-        data: undefined,
-        called: true,
-        loading: true,
-        networkStatus: NetworkStatus.loading,
-        previousData: undefined,
-        variables: { id: 1 },
-      });
-    }
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { hello: "world 1" },
         called: true,
         loading: false,
@@ -233,35 +167,23 @@ describe("useLazyQuery Hook", () => {
         variables: { id: 1 },
       });
     }
-  });
 
-  it("should use variables passed into lazy execution function, overriding similar variables defined in Hook options", async () => {
-    const query = gql`
-      query ($id: number) {
-        hello(id: $id)
-      }
-    `;
+    await expect(takeSnapshot).not.toRerender();
+  });
 
+  test("sets initial loading state when notifyOnNetworkStatusChange is true", async () => {
     const mocks = [
       {
-        request: { query, variables: { id: 1 } },
-        result: { data: { hello: "world 1" } },
-        delay: 20,
-      },
-      {
-        request: { query, variables: { id: 2 } },
-        result: { data: { hello: "world 2" } },
-        delay: 20,
+        request: { query: helloQuery },
+        result: { data: { hello: "world" } },
+        delay: 50,
       },
     ];
 
     using _disabledAct = disableActEnvironment();
     const { takeSnapshot, getCurrentSnapshot } =
       await renderHookToSnapshotStream(
-        () =>
-          useLazyQuery(query, {
-            variables: { id: 1 },
-          }),
+        () => useLazyQuery(helloQuery, { notifyOnNetworkStatusChange: true }),
         {
           wrapper: ({ children }) => (
             <MockedProvider mocks={mocks}>{children}</MockedProvider>
@@ -272,226 +194,143 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
         previousData: undefined,
-        variables: { id: 1 },
+        variables: {},
       });
     }
 
     const [execute] = getCurrentSnapshot();
-    setTimeout(() => execute({ variables: { id: 2 } }));
+    const result = await execute();
+
+    expect(result).toEqualApolloQueryResult({
+      data: { hello: "world" },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
         called: true,
         loading: true,
         networkStatus: NetworkStatus.loading,
         previousData: undefined,
-        variables: { id: 2 },
+        variables: {},
       });
     }
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: { hello: "world 2" },
+      expect(result).toEqualLazyQueryResult({
+        data: { hello: "world" },
         called: true,
         loading: false,
         networkStatus: NetworkStatus.ready,
         previousData: undefined,
-        variables: { id: 2 },
+        variables: {},
       });
     }
+
+    await expect(takeSnapshot).not.toRerender();
   });
 
-  it("should merge variables from original hook and execution function", async () => {
-    const counterQuery: TypedDocumentNode<
-      {
-        counter: number;
-        vars: Record<string, boolean>;
-      },
-      {
-        hookVar?: boolean;
-        execVar?: boolean;
-        localDefaultVar?: boolean;
-        globalDefaultVar?: boolean;
+  it("changing queries", async () => {
+    const query1 = gql`
+      query {
+        hello
       }
-    > = gql`
-      query GetCounter(
-        $hookVar: Boolean
-        $execVar: Boolean
-        $localDefaultVar: Boolean
-        $globalDefaultVar: Boolean
-      ) {
-        counter
-        vars
+    `;
+    const query2 = gql`
+      query {
+        name
       }
     `;
-
-    let count = 0;
-    const client = new ApolloClient({
-      defaultOptions: {
-        watchQuery: {
-          variables: {
-            globalDefaultVar: true,
-          },
-        },
+    const mocks = [
+      {
+        request: { query: query1 },
+        result: { data: { hello: "world" } },
+        delay: 20,
       },
-      cache: new InMemoryCache(),
-      link: new ApolloLink(
-        (request) =>
-          new Observable((observer) => {
-            if (request.operationName === "GetCounter") {
-              setTimeout(() => {
-                observer.next({
-                  data: {
-                    counter: ++count,
-                    vars: request.variables,
-                  },
-                });
-                observer.complete();
-              }, 50);
-            } else {
-              observer.error(
-                new Error(
-                  `Unknown query: ${request.operationName || request.query}`
-                )
-              );
-            }
-          })
-      ),
-    });
+      {
+        request: { query: query2 },
+        result: { data: { name: "changed" } },
+        delay: 20,
+      },
+    ];
 
-    using __disabledAct = disableActEnvironment();
-    const { takeSnapshot, getCurrentSnapshot } =
-      await renderHookToSnapshotStream(
-        () => {
-          return useLazyQuery(counterQuery, {
-            notifyOnNetworkStatusChange: true,
-            variables: {
-              hookVar: true,
-            },
-            defaultOptions: {
-              variables: {
-                localDefaultVar: true,
-              },
-            },
-          });
-        },
-        {
-          wrapper: ({ children }) => (
-            <ApolloProvider client={client}>{children}</ApolloProvider>
-          ),
-        }
-      );
+    const cache = new InMemoryCache();
+    using _disabledAct = disableActEnvironment();
+    const { takeSnapshot, getCurrentSnapshot, rerender } =
+      await renderHookToSnapshotStream(({ query }) => useLazyQuery(query), {
+        initialProps: { query: query1 },
+        wrapper: ({ children }) => (
+          <MockedProvider mocks={mocks} cache={cache}>
+            {children}
+          </MockedProvider>
+        ),
+      });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
         previousData: undefined,
-        variables: {
-          globalDefaultVar: true,
-          localDefaultVar: true,
-          hookVar: true,
-        },
+        variables: {},
       });
     }
 
-    const expectedFinalData = {
-      counter: 1,
-      vars: {
-        globalDefaultVar: true,
-        localDefaultVar: true,
-        hookVar: true,
-        execVar: true,
-      },
-    };
-
     const [execute] = getCurrentSnapshot();
-    const execResult = await execute({
-      variables: {
-        execVar: true,
-      },
-    });
 
-    // TODO: Determine if the return value makes sense. Other fetching functions
-    // (`refetch`, `fetchMore`, etc.) resolve with an `ApolloQueryResult` type
-    // which contain a subset of this data.
-    expect(execResult).toEqualQueryResult({
-      data: expectedFinalData,
-      called: true,
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { hello: "world" },
       loading: false,
       networkStatus: NetworkStatus.ready,
-      previousData: undefined,
-      variables: {
-        globalDefaultVar: true,
-        localDefaultVar: true,
-        hookVar: true,
-        execVar: true,
-      },
+      partial: false,
     });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: undefined,
+      expect(result).toEqualLazyQueryResult({
+        data: { hello: "world" },
         called: true,
-        loading: true,
-        networkStatus: NetworkStatus.loading,
+        loading: false,
+        networkStatus: NetworkStatus.ready,
         previousData: undefined,
-        variables: {
-          globalDefaultVar: true,
-          localDefaultVar: true,
-          hookVar: true,
-          execVar: true,
-        },
+        variables: {},
       });
     }
 
+    await rerender({ query: query2 });
+
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: expectedFinalData,
+      expect(result).toEqualLazyQueryResult({
+        data: { hello: "world" },
         called: true,
         loading: false,
         networkStatus: NetworkStatus.ready,
         previousData: undefined,
-        variables: {
-          globalDefaultVar: true,
-          localDefaultVar: true,
-          hookVar: true,
-          execVar: true,
-        },
+        variables: {},
       });
     }
 
-    const refetchResult = await getCurrentSnapshot()[1].reobserve({
-      fetchPolicy: "network-only",
-      nextFetchPolicy: "cache-first",
-      variables: {
-        execVar: false,
-      },
-    });
-
-    expect(refetchResult).toEqualApolloQueryResult({
-      data: { counter: 2, vars: { execVar: false } },
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { name: "changed" },
       loading: false,
       networkStatus: NetworkStatus.ready,
       partial: false,
@@ -500,111 +339,131 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: expectedFinalData,
-        called: true,
-        loading: true,
-        networkStatus: NetworkStatus.setVariables,
-        previousData: expectedFinalData,
-        variables: { execVar: false },
-      });
-    }
-
-    {
-      const [, result] = await takeSnapshot();
-
-      expect(result).toEqualQueryResult({
-        data: { counter: 2, vars: { execVar: false } },
+      expect(result).toEqualLazyQueryResult({
+        data: { name: "changed" },
         called: true,
         loading: false,
         networkStatus: NetworkStatus.ready,
-        previousData: expectedFinalData,
-        variables: { execVar: false },
+        previousData: { hello: "world" },
+        variables: {},
       });
     }
 
-    const execResult2 = await getCurrentSnapshot()[0]({
-      fetchPolicy: "cache-and-network",
-      nextFetchPolicy: "cache-first",
-      variables: {
-        execVar: true,
-      },
-    });
+    await expect(takeSnapshot).not.toRerender();
+  });
 
-    expect(execResult2).toEqualQueryResult({
-      data: { counter: 3, vars: { ...expectedFinalData.vars, execVar: true } },
-      called: true,
-      loading: false,
-      networkStatus: NetworkStatus.ready,
-      previousData: { counter: 2, vars: { execVar: false } },
-      variables: {
-        globalDefaultVar: true,
-        localDefaultVar: true,
-        hookVar: true,
-        execVar: true,
+  it("applies changed query to next refetch after execute", async () => {
+    const query1 = gql`
+      query {
+        hello
+      }
+    `;
+    const query2 = gql`
+      query {
+        name
+      }
+    `;
+    const mocks = [
+      {
+        request: { query: query1 },
+        result: { data: { hello: "world" } },
+        delay: 20,
       },
-    });
+      {
+        request: { query: query2 },
+        result: { data: { name: "changed" } },
+        delay: 20,
+      },
+    ];
+
+    const cache = new InMemoryCache();
+    using _disabledAct = disableActEnvironment();
+    const { takeSnapshot, getCurrentSnapshot, rerender } =
+      await renderHookToSnapshotStream(({ query }) => useLazyQuery(query), {
+        initialProps: { query: query1 },
+        wrapper: ({ children }) => (
+          <MockedProvider mocks={mocks} cache={cache}>
+            {children}
+          </MockedProvider>
+        ),
+      });
+
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: undefined,
+        called: false,
+        loading: false,
+        networkStatus: NetworkStatus.ready,
+        previousData: undefined,
+        variables: {},
+      });
+    }
+
+    const [execute] = getCurrentSnapshot();
+
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { hello: "world" },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: { counter: 2, vars: { execVar: false } },
+      expect(result).toEqualLazyQueryResult({
+        data: { hello: "world" },
         called: true,
-        loading: true,
-        networkStatus: NetworkStatus.setVariables,
-        previousData: { counter: 2, vars: { execVar: false } },
-        variables: {
-          globalDefaultVar: true,
-          localDefaultVar: true,
-          hookVar: true,
-          execVar: true,
-        },
+        loading: false,
+        networkStatus: NetworkStatus.ready,
+        previousData: undefined,
+        variables: {},
       });
     }
 
-    // For some reason we get an extra render in React 18 of the same thing
-    if (IS_REACT_18) {
+    await rerender({ query: query2 });
+
+    {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: { counter: 2, vars: { execVar: false } },
+      expect(result).toEqualLazyQueryResult({
+        data: { hello: "world" },
         called: true,
-        loading: true,
-        networkStatus: NetworkStatus.setVariables,
-        previousData: { counter: 2, vars: { execVar: false } },
-        variables: {
-          globalDefaultVar: true,
-          localDefaultVar: true,
-          hookVar: true,
-          execVar: true,
-        },
+        loading: false,
+        networkStatus: NetworkStatus.ready,
+        previousData: undefined,
+        variables: {},
       });
     }
 
+    const [, { refetch }] = getCurrentSnapshot();
+
+    await expect(refetch()).resolves.toEqualApolloQueryResult({
+      data: { name: "changed" },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
+
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: {
-          counter: 3,
-          vars: { ...expectedFinalData.vars, execVar: true },
-        },
+      expect(result).toEqualLazyQueryResult({
+        data: { name: "changed" },
         called: true,
         loading: false,
         networkStatus: NetworkStatus.ready,
-        previousData: { counter: 2, vars: { execVar: false } },
-        variables: {
-          globalDefaultVar: true,
-          localDefaultVar: true,
-          hookVar: true,
-          execVar: true,
-        },
+        previousData: { hello: "world" },
+        variables: {},
       });
     }
+
+    await expect(takeSnapshot).not.toRerender();
   });
 
-  it("changing queries", async () => {
+  test("renders loading states when changing queries with notifyOnNetworkStatusChange", async () => {
     const query1 = gql`
       query {
         hello
@@ -630,21 +489,25 @@ describe("useLazyQuery Hook", () => {
 
     const cache = new InMemoryCache();
     using _disabledAct = disableActEnvironment();
-    const { takeSnapshot, getCurrentSnapshot } =
-      await renderHookToSnapshotStream(() => useLazyQuery(query1), {
-        wrapper: ({ children }) => (
-          <MockedProvider mocks={mocks} cache={cache}>
-            {children}
-          </MockedProvider>
-        ),
-      });
+    const { takeSnapshot, getCurrentSnapshot, rerender } =
+      await renderHookToSnapshotStream(
+        ({ query }) =>
+          useLazyQuery(query, { notifyOnNetworkStatusChange: true }),
+        {
+          initialProps: { query: query1 },
+          wrapper: ({ children }) => (
+            <MockedProvider mocks={mocks} cache={cache}>
+              {children}
+            </MockedProvider>
+          ),
+        }
+      );
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
@@ -653,13 +516,19 @@ describe("useLazyQuery Hook", () => {
       });
     }
 
-    const execute = getCurrentSnapshot()[0];
-    setTimeout(() => execute());
+    const [execute] = getCurrentSnapshot();
+
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { hello: "world" },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
         called: true,
         loading: true,
@@ -671,7 +540,7 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { hello: "world" },
         called: true,
         loading: false,
@@ -681,14 +550,34 @@ describe("useLazyQuery Hook", () => {
       });
     }
 
-    setTimeout(() => execute({ query: query2 }));
+    await rerender({ query: query2 });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { hello: "world" },
         called: true,
+        loading: false,
+        networkStatus: NetworkStatus.ready,
+        previousData: undefined,
+        variables: {},
+      });
+    }
+
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { name: "changed" },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
+
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: undefined,
+        called: true,
         loading: true,
         networkStatus: NetworkStatus.loading,
         previousData: { hello: "world" },
@@ -699,7 +588,7 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { name: "changed" },
         called: true,
         loading: false,
@@ -708,6 +597,8 @@ describe("useLazyQuery Hook", () => {
         variables: {},
       });
     }
+
+    await expect(takeSnapshot).not.toRerender();
   });
 
   it('should fetch data each time the execution function is called, when using a "network-only" fetch policy', async () => {
@@ -741,23 +632,116 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
         called: false,
-        error: undefined,
         loading: false,
         networkStatus: NetworkStatus.ready,
         previousData: undefined,
         variables: {},
       });
     }
-    const execute = getCurrentSnapshot()[0];
-    setTimeout(() => execute());
+
+    const [execute] = getCurrentSnapshot();
+
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { hello: "world 1" },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
+
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: { hello: "world 1" },
+        called: true,
+        loading: false,
+        networkStatus: NetworkStatus.ready,
+        previousData: undefined,
+        variables: {},
+      });
+    }
+
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { hello: "world 2" },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
+
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: { hello: "world 2" },
+        called: true,
+        loading: false,
+        networkStatus: NetworkStatus.ready,
+        previousData: { hello: "world 1" },
+        variables: {},
+      });
+    }
+
+    await expect(takeSnapshot).not.toRerender();
+  });
+
+  it('renders loading states each time the execution function is called when using a "network-only" fetch policy with notifyOnNetworkStatusChange', async () => {
+    const mocks = [
+      {
+        request: { query: helloQuery },
+        result: { data: { hello: "world 1" } },
+        delay: 20,
+      },
+      {
+        request: { query: helloQuery },
+        result: { data: { hello: "world 2" } },
+        delay: 20,
+      },
+    ];
+
+    using _disabledAct = disableActEnvironment();
+    const { takeSnapshot, getCurrentSnapshot } =
+      await renderHookToSnapshotStream(
+        () =>
+          useLazyQuery(helloQuery, {
+            notifyOnNetworkStatusChange: true,
+            fetchPolicy: "network-only",
+          }),
+        {
+          wrapper: ({ children }) => (
+            <MockedProvider mocks={mocks}>{children}</MockedProvider>
+          ),
+        }
+      );
+
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: undefined,
+        called: false,
+        loading: false,
+        networkStatus: NetworkStatus.ready,
+        previousData: undefined,
+        variables: {},
+      });
+    }
+
+    const [execute] = getCurrentSnapshot();
+
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { hello: "world 1" },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
         called: true,
         loading: true,
@@ -770,7 +754,7 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { hello: "world 1" },
         called: true,
         loading: false,
@@ -780,24 +764,30 @@ describe("useLazyQuery Hook", () => {
       });
     }
 
-    setTimeout(() => execute());
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { hello: "world 2" },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { hello: "world 1" },
         called: true,
         loading: true,
         networkStatus: NetworkStatus.loading,
-        previousData: { hello: "world 1" },
+        previousData: undefined,
         variables: {},
       });
     }
+
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { hello: "world 2" },
         called: true,
         loading: false,
@@ -806,9 +796,11 @@ describe("useLazyQuery Hook", () => {
         variables: {},
       });
     }
+
+    await expect(takeSnapshot).not.toRerender();
   });
 
-  it("should persist previous data when a query is re-run", async () => {
+  it("should persist previous data when a query is refetched", async () => {
     const mocks = [
       {
         request: { query: helloQuery },
@@ -839,9 +831,8 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
@@ -849,13 +840,20 @@ describe("useLazyQuery Hook", () => {
         variables: {},
       });
     }
-    const execute = getCurrentSnapshot()[0];
-    setTimeout(() => execute());
+
+    const [execute] = getCurrentSnapshot();
+
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { hello: "world 1" },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
         called: true,
         loading: true,
@@ -868,7 +866,7 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { hello: "world 1" },
         called: true,
         loading: false,
@@ -878,25 +876,31 @@ describe("useLazyQuery Hook", () => {
       });
     }
 
-    const refetch = getCurrentSnapshot()[1].refetch;
-    setTimeout(() => refetch!());
+    const [, { refetch }] = getCurrentSnapshot();
+
+    await expect(refetch()).resolves.toEqualApolloQueryResult({
+      data: { hello: "world 2" },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { hello: "world 1" },
         called: true,
         loading: true,
         networkStatus: NetworkStatus.refetch,
-        previousData: { hello: "world 1" },
+        previousData: undefined,
         variables: {},
       });
     }
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { hello: "world 2" },
         called: true,
         loading: false,
@@ -905,8 +909,13 @@ describe("useLazyQuery Hook", () => {
         variables: {},
       });
     }
+
+    await expect(takeSnapshot).not.toRerender();
   });
 
+  // TODO: Determine if this hook makes sense for polling or if that should be
+  // reserved for useQuery. At the very least, we need to figure out if you can
+  // start polling a query before it has been executed
   it("should allow for the query to start with polling", async () => {
     const mocks = [
       {
@@ -937,9 +946,8 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
@@ -948,26 +956,19 @@ describe("useLazyQuery Hook", () => {
       });
     }
 
-    await tick();
-    getCurrentSnapshot()[1].startPolling(10);
-
-    {
-      const [, result] = await takeSnapshot();
+    const [execute] = getCurrentSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: undefined,
-        called: true,
-        loading: true,
-        networkStatus: NetworkStatus.loading,
-        previousData: undefined,
-        variables: {},
-      });
-    }
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { hello: "world 1" },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { hello: "world 1" },
         called: true,
         loading: false,
@@ -977,10 +978,12 @@ describe("useLazyQuery Hook", () => {
       });
     }
 
+    getCurrentSnapshot()[1].startPolling(10);
+
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { hello: "world 2" },
         called: true,
         loading: false,
@@ -993,7 +996,7 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { hello: "world 3" },
         called: true,
         loading: false,
@@ -1005,7 +1008,7 @@ describe("useLazyQuery Hook", () => {
 
     getCurrentSnapshot()[1].stopPolling();
 
-    expect(takeSnapshot).not.toRerender();
+    await expect(takeSnapshot).not.toRerender();
   });
 
   it("should persist previous data when a query is re-run and variable changes", async () => {
@@ -1058,9 +1061,8 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
@@ -1068,25 +1070,22 @@ describe("useLazyQuery Hook", () => {
         variables: {},
       });
     }
-    const execute = getCurrentSnapshot()[0];
-    setTimeout(() => execute({ variables: { id: 1 } }));
 
-    {
-      const [, result] = await takeSnapshot();
+    const [execute] = getCurrentSnapshot();
+
+    await expect(
+      execute({ variables: { id: 1 } })
+    ).resolves.toEqualApolloQueryResult({
+      data: data1,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
 
-      expect(result).toEqualQueryResult({
-        data: undefined,
-        called: true,
-        loading: true,
-        networkStatus: NetworkStatus.loading,
-        previousData: undefined,
-        variables: { id: 1 },
-      });
-    }
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: data1,
         called: true,
         loading: false,
@@ -1096,12 +1095,145 @@ describe("useLazyQuery Hook", () => {
       });
     }
 
-    setTimeout(() => execute({ variables: { id: 2 } }));
-
+    await expect(
+      execute({ variables: { id: 2 } })
+    ).resolves.toEqualApolloQueryResult({
+      data: data2,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
+
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: data2,
+        called: true,
+        loading: false,
+        networkStatus: NetworkStatus.ready,
+        previousData: data1,
+        variables: { id: 2 },
+      });
+    }
+
+    await expect(takeSnapshot).not.toRerender();
+  });
+
+  test("renders loading states when a query is re-run and variables changes with notifyOnNetworkStatusChange", async () => {
+    const CAR_QUERY_BY_ID = gql`
+      query ($id: Int) {
+        car(id: $id) {
+          make
+          model
+        }
+      }
+    `;
+
+    const data1 = {
+      car: {
+        make: "Audi",
+        model: "A4",
+        __typename: "Car",
+      },
+    };
+
+    const data2 = {
+      car: {
+        make: "Audi",
+        model: "RS8",
+        __typename: "Car",
+      },
+    };
+
+    const mocks = [
+      {
+        request: { query: CAR_QUERY_BY_ID, variables: { id: 1 } },
+        result: { data: data1 },
+        delay: 20,
+      },
+      {
+        request: { query: CAR_QUERY_BY_ID, variables: { id: 2 } },
+        result: { data: data2 },
+        delay: 20,
+      },
+    ];
+
+    using _disabledAct = disableActEnvironment();
+    const { takeSnapshot, getCurrentSnapshot } =
+      await renderHookToSnapshotStream(
+        () =>
+          useLazyQuery(CAR_QUERY_BY_ID, { notifyOnNetworkStatusChange: true }),
+        {
+          wrapper: ({ children }) => (
+            <MockedProvider mocks={mocks}>{children}</MockedProvider>
+          ),
+        }
+      );
+
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: undefined,
+        called: false,
+        loading: false,
+        networkStatus: NetworkStatus.ready,
+        previousData: undefined,
+        variables: {},
+      });
+    }
+
+    const [execute] = getCurrentSnapshot();
+
+    await expect(
+      execute({ variables: { id: 1 } })
+    ).resolves.toEqualApolloQueryResult({
+      data: data1,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
+
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: undefined,
+        called: true,
+        loading: true,
+        networkStatus: NetworkStatus.loading,
+        previousData: undefined,
+        variables: { id: 1 },
+      });
+    }
+
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: data1,
+        called: true,
+        loading: false,
+        networkStatus: NetworkStatus.ready,
+        previousData: undefined,
+        variables: { id: 1 },
+      });
+    }
+
+    await expect(
+      execute({ variables: { id: 2 } })
+    ).resolves.toEqualApolloQueryResult({
+      data: data2,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
+
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
         called: true,
         loading: true,
@@ -1113,7 +1245,7 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: data2,
         called: true,
         loading: false,
@@ -1122,6 +1254,8 @@ describe("useLazyQuery Hook", () => {
         variables: { id: 2 },
       });
     }
+
+    await expect(takeSnapshot).not.toRerender();
   });
 
   it("should work with cache-and-network fetch policy", async () => {
@@ -1153,9 +1287,8 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
@@ -1166,13 +1299,17 @@ describe("useLazyQuery Hook", () => {
 
     const [execute] = getCurrentSnapshot();
 
-    setTimeout(() => execute());
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { hello: "from link" },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        // TODO: FIXME
+      expect(result).toEqualLazyQueryResult({
         data: { hello: "from cache" },
         called: true,
         loading: true,
@@ -1185,7 +1322,7 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { hello: "from link" },
         called: true,
         loading: false,
@@ -1194,31 +1331,48 @@ describe("useLazyQuery Hook", () => {
         variables: {},
       });
     }
+
+    await expect(takeSnapshot).not.toRerender();
   });
 
-  it("should return a promise from the execution function which resolves with the result", async () => {
-    const mocks = [
+  test("executes on the network multiple times with a cache-and-network fetch policy", async () => {
+    const cache = new InMemoryCache();
+    const link = new MockLink([
       {
         request: { query: helloQuery },
-        result: { data: { hello: "world" } },
+        result: { data: { hello: "from link" } },
         delay: 20,
       },
-    ];
+      {
+        request: { query: helloQuery },
+        result: { data: { hello: "from link 2" } },
+        delay: 20,
+      },
+    ]);
+
+    const client = new ApolloClient({
+      link,
+      cache,
+    });
+
+    cache.writeQuery({ query: helloQuery, data: { hello: "from cache" } });
 
     using _disabledAct = disableActEnvironment();
     const { takeSnapshot, getCurrentSnapshot } =
-      await renderHookToSnapshotStream(() => useLazyQuery(helloQuery), {
-        wrapper: ({ children }) => (
-          <MockedProvider mocks={mocks}>{children}</MockedProvider>
-        ),
-      });
+      await renderHookToSnapshotStream(
+        () => useLazyQuery(helloQuery, { fetchPolicy: "cache-and-network" }),
+        {
+          wrapper: ({ children }) => (
+            <ApolloProvider client={client}>{children}</ApolloProvider>
+          ),
+        }
+      );
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
@@ -1229,14 +1383,18 @@ describe("useLazyQuery Hook", () => {
 
     const [execute] = getCurrentSnapshot();
 
-    await tick();
-    const executeResult = execute();
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { hello: "from link" },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: undefined,
+      expect(result).toEqualLazyQueryResult({
+        data: { hello: "from cache" },
         called: true,
         loading: true,
         networkStatus: NetworkStatus.loading,
@@ -1248,180 +1406,341 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: { hello: "world" },
+      expect(result).toEqualLazyQueryResult({
+        data: { hello: "from link" },
         called: true,
         loading: false,
         networkStatus: NetworkStatus.ready,
-        previousData: undefined,
+        previousData: { hello: "from cache" },
         variables: {},
       });
     }
 
-    await expect(executeResult).resolves.toEqualQueryResult({
-      data: { hello: "world" },
-      called: true,
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { hello: "from link 2" },
       loading: false,
       networkStatus: NetworkStatus.ready,
-      previousData: undefined,
-      variables: {},
+      partial: false,
     });
+
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: { hello: "from link" },
+        called: true,
+        loading: true,
+        networkStatus: NetworkStatus.loading,
+        previousData: { hello: "from cache" },
+        variables: {},
+      });
+    }
+
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: { hello: "from link 2" },
+        called: true,
+        loading: false,
+        networkStatus: NetworkStatus.ready,
+        previousData: { hello: "from link" },
+        variables: {},
+      });
+    }
+
+    await expect(takeSnapshot).not.toRerender();
   });
 
-  it("should have matching results from execution function and hook", async () => {
-    const query = gql`
-      query GetCountries($filter: String) {
-        countries(filter: $filter) {
-          code
-          name
-        }
-      }
-    `;
+  test("executes on the network multiple times with a cache-and-network fetch policy when changing variables", async () => {
+    const { query, mocks } = setupVariablesCase();
 
-    const mocks = [
-      {
-        request: {
-          query,
-          variables: {
-            filter: "PA",
-          },
-        },
-        result: {
-          data: {
-            countries: {
-              code: "PA",
-              name: "Panama",
-            },
-          },
-        },
-        delay: 20,
+    const client = new ApolloClient({
+      cache: new InMemoryCache(),
+      link: new MockLink(mocks),
+    });
+
+    client.writeQuery({
+      query,
+      data: {
+        character: { __typename: "Character", id: "1", name: "Cache 1" },
       },
-      {
-        request: {
-          query,
-          variables: {
-            filter: "BA",
-          },
-        },
-        result: {
-          data: {
-            countries: {
-              code: "BA",
-              name: "Bahamas",
-            },
-          },
-        },
-        delay: 20,
+      variables: { id: "1" },
+    });
+
+    client.writeQuery({
+      query,
+      data: {
+        character: { __typename: "Character", id: "2", name: "Cache 2" },
       },
-    ];
+      variables: { id: "2" },
+    });
 
     using _disabledAct = disableActEnvironment();
     const { takeSnapshot, getCurrentSnapshot } =
-      await renderHookToSnapshotStream(() => useLazyQuery(query), {
-        wrapper: ({ children }) => (
-          <MockedProvider mocks={mocks}>{children}</MockedProvider>
-        ),
-      });
+      await renderHookToSnapshotStream(
+        () => useLazyQuery(query, { fetchPolicy: "cache-and-network" }),
+        {
+          wrapper: ({ children }) => (
+            <ApolloProvider client={client}>{children}</ApolloProvider>
+          ),
+        }
+      );
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
         previousData: undefined,
+        // @ts-expect-error should be undefined
         variables: {},
       });
     }
 
     const [execute] = getCurrentSnapshot();
 
-    await tick();
-    let executeResult = execute({ variables: { filter: "PA" } });
+    await expect(
+      execute({ variables: { id: "1" } })
+    ).resolves.toEqualApolloQueryResult({
+      data: {
+        character: { __typename: "Character", id: "1", name: "Spider-Man" },
+      },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: undefined,
+      expect(result).toEqualLazyQueryResult({
+        data: {
+          character: { __typename: "Character", id: "1", name: "Cache 1" },
+        },
         called: true,
         loading: true,
         networkStatus: NetworkStatus.loading,
         previousData: undefined,
-        variables: { filter: "PA" },
+        variables: { id: "1" },
       });
     }
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: { countries: { code: "PA", name: "Panama" } },
+      expect(result).toEqualLazyQueryResult({
+        data: {
+          character: { __typename: "Character", id: "1", name: "Spider-Man" },
+        },
         called: true,
         loading: false,
         networkStatus: NetworkStatus.ready,
-        previousData: undefined,
-        variables: { filter: "PA" },
+        previousData: {
+          character: { __typename: "Character", id: "1", name: "Cache 1" },
+        },
+        variables: { id: "1" },
       });
     }
 
-    await expect(executeResult).resolves.toEqualQueryResult({
+    await expect(
+      execute({ variables: { id: "2" } })
+    ).resolves.toEqualApolloQueryResult({
       data: {
-        countries: {
-          code: "PA",
-          name: "Panama",
-        },
+        character: { __typename: "Character", id: "2", name: "Black Widow" },
       },
-      called: true,
       loading: false,
       networkStatus: NetworkStatus.ready,
-      previousData: undefined,
-      variables: { filter: "PA" },
+      partial: false,
+    });
+
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: {
+          character: { __typename: "Character", id: "2", name: "Cache 2" },
+        },
+        called: true,
+        loading: true,
+        networkStatus: NetworkStatus.setVariables,
+        previousData: {
+          character: { __typename: "Character", id: "1", name: "Spider-Man" },
+        },
+        variables: { id: "2" },
+      });
+    }
+
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: {
+          character: { __typename: "Character", id: "2", name: "Black Widow" },
+        },
+        called: true,
+        loading: false,
+        networkStatus: NetworkStatus.ready,
+        previousData: {
+          character: { __typename: "Character", id: "2", name: "Cache 2" },
+        },
+        variables: { id: "2" },
+      });
+    }
+
+    await expect(takeSnapshot).not.toRerender();
+  });
+
+  test("renders loading states with a cache-and-network fetch policy when changing variables with notifyOnNetworkStatusChange", async () => {
+    const { query, mocks } = setupVariablesCase();
+
+    const client = new ApolloClient({
+      cache: new InMemoryCache(),
+      link: new MockLink(mocks),
+    });
+
+    client.writeQuery({
+      query,
+      data: {
+        character: { __typename: "Character", id: "1", name: "Cache 1" },
+      },
+      variables: { id: "1" },
+    });
+
+    client.writeQuery({
+      query,
+      data: {
+        character: { __typename: "Character", id: "2", name: "Cache 2" },
+      },
+      variables: { id: "2" },
     });
 
-    await tick();
-    executeResult = execute({ variables: { filter: "BA" } });
+    using _disabledAct = disableActEnvironment();
+    const { takeSnapshot, getCurrentSnapshot } =
+      await renderHookToSnapshotStream(
+        () =>
+          useLazyQuery(query, {
+            fetchPolicy: "cache-and-network",
+            notifyOnNetworkStatusChange: true,
+          }),
+        {
+          wrapper: ({ children }) => (
+            <ApolloProvider client={client}>{children}</ApolloProvider>
+          ),
+        }
+      );
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
+        called: false,
+        loading: false,
+        networkStatus: NetworkStatus.ready,
+        previousData: undefined,
+        // @ts-expect-error should be undefined
+        variables: {},
+      });
+    }
+
+    const [execute] = getCurrentSnapshot();
+
+    await expect(
+      execute({ variables: { id: "1" } })
+    ).resolves.toEqualApolloQueryResult({
+      data: {
+        character: { __typename: "Character", id: "1", name: "Spider-Man" },
+      },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
+
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: {
+          character: { __typename: "Character", id: "1", name: "Cache 1" },
+        },
         called: true,
         loading: true,
-        networkStatus: NetworkStatus.setVariables,
-        previousData: { countries: { code: "PA", name: "Panama" } },
-        variables: { filter: "BA" },
+        networkStatus: NetworkStatus.loading,
+        previousData: undefined,
+        variables: { id: "1" },
       });
     }
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: { countries: { code: "BA", name: "Bahamas" } },
+      expect(result).toEqualLazyQueryResult({
+        data: {
+          character: { __typename: "Character", id: "1", name: "Spider-Man" },
+        },
         called: true,
         loading: false,
         networkStatus: NetworkStatus.ready,
-        previousData: { countries: { code: "PA", name: "Panama" } },
-        variables: { filter: "BA" },
+        previousData: {
+          character: { __typename: "Character", id: "1", name: "Cache 1" },
+        },
+        variables: { id: "1" },
       });
     }
 
-    await expect(executeResult).resolves.toEqualQueryResult({
-      data: { countries: { code: "BA", name: "Bahamas" } },
-      called: true,
+    await expect(
+      execute({ variables: { id: "2" } })
+    ).resolves.toEqualApolloQueryResult({
+      data: {
+        character: { __typename: "Character", id: "2", name: "Black Widow" },
+      },
       loading: false,
       networkStatus: NetworkStatus.ready,
-      previousData: { countries: { code: "PA", name: "Panama" } },
-      variables: { filter: "BA" },
+      partial: false,
     });
+
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: {
+          character: { __typename: "Character", id: "2", name: "Cache 2" },
+        },
+        called: true,
+        loading: true,
+        networkStatus: NetworkStatus.setVariables,
+        previousData: {
+          character: { __typename: "Character", id: "1", name: "Spider-Man" },
+        },
+        variables: { id: "2" },
+      });
+    }
+
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: {
+          character: { __typename: "Character", id: "2", name: "Black Widow" },
+        },
+        called: true,
+        loading: false,
+        networkStatus: NetworkStatus.ready,
+        previousData: {
+          character: { __typename: "Character", id: "2", name: "Cache 2" },
+        },
+        variables: { id: "2" },
+      });
+    }
+
+    await expect(takeSnapshot).not.toRerender();
   });
 
-  it("the promise should reject with errors the “way useMutation does”", async () => {
+  it("the promise returned from execute rejects when GraphQL errors are returned and errorPolicy is `none`", async () => {
     const mocks = [
       {
         request: { query: helloQuery },
@@ -1454,9 +1773,8 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
@@ -1465,71 +1783,150 @@ describe("useLazyQuery Hook", () => {
       });
     }
 
-    const executePromise = Promise.resolve().then(() => execute());
+    // TODO: Determine if this is the correct behavior. This is different than
+    // 3.x where this resolves with an `ApolloQueryResult`.
+    // https://github.com/apollographql/apollo-client/issues/10787 wants this
+    // behavior
+    // https://github.com/apollographql/apollo-client/issues/9142#issuecomment-1118972947
+    // justifies the old behavior
+    await expect(execute()).rejects.toEqual(
+      new ApolloError({ graphQLErrors: [{ message: "error 1" }] })
+    );
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
         called: true,
-        loading: true,
-        networkStatus: NetworkStatus.loading,
+        loading: false,
+        networkStatus: NetworkStatus.error,
         previousData: undefined,
+        error: new ApolloError({ graphQLErrors: [{ message: "error 1" }] }),
         variables: {},
       });
     }
 
+    await expect(execute()).rejects.toEqual(
+      new ApolloError({ graphQLErrors: [{ message: "error 2" }] })
+    );
+
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
         called: true,
         loading: false,
         networkStatus: NetworkStatus.error,
         previousData: undefined,
-        error: new ApolloError({ graphQLErrors: [{ message: "error 1" }] }),
+        error: new ApolloError({ graphQLErrors: [{ message: "error 2" }] }),
         variables: {},
       });
     }
 
-    await expect(executePromise).resolves.toEqualQueryResult({
-      data: undefined,
-      called: true,
+    await expect(takeSnapshot).not.toRerender();
+  });
+
+  it("the promise returned from execute resolves when GraphQL errors are returned and errorPolicy is `all`", async () => {
+    const query: TypedDocumentNode<{
+      currentUser: { __typename: "User"; id: string } | null;
+    }> = gql`
+      query currentUser {
+        id
+      }
+    `;
+
+    const mocks = [
+      {
+        request: { query },
+        result: {
+          data: { currentUser: null },
+          errors: [{ message: "Not logged in" }],
+        },
+        delay: 20,
+      },
+      {
+        request: { query },
+        result: {
+          data: { currentUser: null },
+          errors: [{ message: "Not logged in 2" }],
+        },
+        delay: 20,
+      },
+    ];
+
+    using _disabledAct = disableActEnvironment();
+    const { takeSnapshot, peekSnapshot } = await renderHookToSnapshotStream(
+      () => useLazyQuery(query, { errorPolicy: "all" }),
+      {
+        wrapper: ({ children }) => (
+          <MockedProvider mocks={mocks}>{children}</MockedProvider>
+        ),
+      }
+    );
+
+    const [execute] = await peekSnapshot();
+
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: undefined,
+        called: false,
+        loading: false,
+        networkStatus: NetworkStatus.ready,
+        previousData: undefined,
+        variables: {},
+      });
+    }
+
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { currentUser: null },
+      error: new ApolloError({ graphQLErrors: [{ message: "Not logged in" }] }),
       loading: false,
       networkStatus: NetworkStatus.error,
-      previousData: undefined,
-      error: new ApolloError({ graphQLErrors: [{ message: "error 1" }] }),
-      variables: {},
+      partial: false,
     });
 
-    void execute();
-
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: undefined,
+      expect(result).toEqualLazyQueryResult({
+        data: { currentUser: null },
         called: true,
-        loading: true,
-        networkStatus: NetworkStatus.loading,
+        loading: false,
+        networkStatus: NetworkStatus.error,
         previousData: undefined,
-        error: new ApolloError({ graphQLErrors: [{ message: "error 1" }] }),
+        error: new ApolloError({
+          graphQLErrors: [{ message: "Not logged in" }],
+        }),
         variables: {},
       });
     }
 
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { currentUser: null },
+      error: new ApolloError({
+        graphQLErrors: [{ message: "Not logged in 2" }],
+      }),
+      loading: false,
+      networkStatus: NetworkStatus.error,
+      partial: false,
+    });
+
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: undefined,
+      expect(result).toEqualLazyQueryResult({
+        data: { currentUser: null },
         called: true,
         loading: false,
         networkStatus: NetworkStatus.error,
         previousData: undefined,
-        error: new ApolloError({ graphQLErrors: [{ message: "error 2" }] }),
+        error: new ApolloError({
+          graphQLErrors: [{ message: "Not logged in 2" }],
+        }),
         variables: {},
       });
     }
@@ -1537,12 +1934,29 @@ describe("useLazyQuery Hook", () => {
     await expect(takeSnapshot).not.toRerender();
   });
 
-  it("the promise should not cause an unhandled rejection", async () => {
+  it("the promise returned from execute resolves when GraphQL errors are returned and errorPolicy is `ignore`", async () => {
+    const query: TypedDocumentNode<{
+      currentUser: { __typename: "User"; id: string } | null;
+    }> = gql`
+      query currentUser {
+        id
+      }
+    `;
+
     const mocks = [
       {
-        request: { query: helloQuery },
+        request: { query },
+        result: {
+          data: { currentUser: null },
+          errors: [{ message: "Not logged in" }],
+        },
+        delay: 20,
+      },
+      {
+        request: { query },
         result: {
-          errors: [new GraphQLError("error 1")],
+          data: { currentUser: null },
+          errors: [{ message: "Not logged in 2" }],
         },
         delay: 20,
       },
@@ -1550,7 +1964,7 @@ describe("useLazyQuery Hook", () => {
 
     using _disabledAct = disableActEnvironment();
     const { takeSnapshot, peekSnapshot } = await renderHookToSnapshotStream(
-      () => useLazyQuery(helloQuery),
+      () => useLazyQuery(query, { errorPolicy: "ignore" }),
       {
         wrapper: ({ children }) => (
           <MockedProvider mocks={mocks}>{children}</MockedProvider>
@@ -1563,9 +1977,8 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
@@ -1574,10 +1987,36 @@ describe("useLazyQuery Hook", () => {
       });
     }
 
-    void execute();
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { currentUser: null },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
 
-    // Making sure the rejection triggers a test failure.
-    await wait(50);
+    {
+      const [, result] = await takeSnapshot();
+
+      expect(result).toEqualLazyQueryResult({
+        data: { currentUser: null },
+        called: true,
+        loading: false,
+        networkStatus: NetworkStatus.ready,
+        previousData: undefined,
+        variables: {},
+      });
+    }
+
+    await expect(execute()).resolves.toEqual({
+      data: { currentUser: null },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
+
+    // We don't see an extra render here since the result is deeply equal to the
+    // previous result.
+    await expect(takeSnapshot).not.toRerender();
   });
 
   it("allows in-flight requests to resolve when component unmounts", async () => {
@@ -1592,7 +2031,7 @@ describe("useLazyQuery Hook", () => {
 
     const [execute] = result.current;
 
-    let promise: Promise<QueryResult<{ hello: string }>>;
+    let promise: Promise<ApolloQueryResult<{ hello: string }>>;
     act(() => {
       promise = execute();
     });
@@ -1601,13 +2040,11 @@ describe("useLazyQuery Hook", () => {
 
     link.simulateResult({ result: { data: { hello: "Greetings" } } }, true);
 
-    await expect(promise!).resolves.toEqualQueryResult({
+    await expect(promise!).resolves.toEqualApolloQueryResult({
       data: { hello: "Greetings" },
-      called: true,
       loading: false,
       networkStatus: NetworkStatus.ready,
-      previousData: undefined,
-      variables: {},
+      partial: false,
     });
   });
 
@@ -1623,8 +2060,8 @@ describe("useLazyQuery Hook", () => {
 
     const [execute] = result.current;
 
-    let promise1: Promise<QueryResult<{ hello: string }>>;
-    let promise2: Promise<QueryResult<{ hello: string }>>;
+    let promise1: Promise<ApolloQueryResult<{ hello: string }>>;
+    let promise2: Promise<ApolloQueryResult<{ hello: string }>>;
     act(() => {
       promise1 = execute();
       promise2 = execute();
@@ -1636,15 +2073,13 @@ describe("useLazyQuery Hook", () => {
 
     const expectedResult = {
       data: { hello: "Greetings" },
-      called: true,
       loading: false,
       networkStatus: NetworkStatus.ready,
-      previousData: undefined,
-      variables: {},
+      partial: false,
     };
 
-    await expect(promise1!).resolves.toEqualQueryResult(expectedResult);
-    await expect(promise2!).resolves.toEqualQueryResult(expectedResult);
+    await expect(promise1!).resolves.toEqualApolloQueryResult(expectedResult);
+    await expect(promise2!).resolves.toEqualApolloQueryResult(expectedResult);
   });
 
   // https://github.com/apollographql/apollo-client/issues/9755
@@ -1694,9 +2129,8 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
@@ -1708,58 +2142,28 @@ describe("useLazyQuery Hook", () => {
     const promise1 = execute({ variables: { id: "1" } });
     const promise2 = execute({ variables: { id: "2" } });
 
-    await expect(promise1).resolves.toEqualQueryResult({
+    await expect(promise1).resolves.toEqualApolloQueryResult({
       data: mocks[0].result.data,
       loading: false,
-      called: true,
       networkStatus: NetworkStatus.ready,
-      previousData: undefined,
-      variables: { id: "2" },
+      partial: false,
     });
 
-    await expect(promise2).resolves.toEqualQueryResult({
+    await expect(promise2).resolves.toEqualApolloQueryResult({
       data: mocks[1].result.data,
       loading: false,
-      called: true,
       networkStatus: NetworkStatus.ready,
-      previousData: undefined,
-      variables: { id: "2" },
+      partial: false,
     });
 
-    if (IS_REACT_17) {
+    {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: undefined,
+      expect(result).toEqualLazyQueryResult({
+        data: mocks[1].result.data,
         called: true,
-        loading: true,
-        networkStatus: NetworkStatus.loading,
-        previousData: undefined,
-        variables: { id: "1" },
-      });
-    }
-
-    {
-      const [, result] = await takeSnapshot();
-
-      expect(result).toEqualQueryResult({
-        data: undefined,
-        called: true,
-        loading: true,
-        networkStatus: NetworkStatus.setVariables,
-        previousData: undefined,
-        variables: { id: "2" },
-      });
-    }
-
-    {
-      const [, result] = await takeSnapshot();
-
-      expect(result).toEqualQueryResult({
-        data: mocks[1].result.data,
-        called: true,
-        loading: false,
-        networkStatus: NetworkStatus.ready,
+        loading: false,
+        networkStatus: NetworkStatus.ready,
         previousData: undefined,
         variables: { id: "2" },
       });
@@ -1768,118 +2172,6 @@ describe("useLazyQuery Hook", () => {
     await expect(takeSnapshot).not.toRerender();
   });
 
-  it("uses the most recent options when the hook rerenders before execution", async () => {
-    interface Data {
-      user: { id: string; name: string };
-    }
-
-    interface Variables {
-      id: string;
-    }
-
-    const query: TypedDocumentNode<Data, Variables> = gql`
-      query UserQuery($id: ID!) {
-        user(id: $id) {
-          id
-          name
-        }
-      }
-    `;
-
-    const mocks = [
-      {
-        request: { query, variables: { id: "1" } },
-        result: { data: { user: { id: "1", name: "John Doe" } } },
-        delay: 30,
-      },
-      {
-        request: { query, variables: { id: "2" } },
-        result: { data: { user: { id: "2", name: "Jane Doe" } } },
-        delay: 20,
-      },
-    ];
-
-    using _disabledAct = disableActEnvironment();
-    const { takeSnapshot, getCurrentSnapshot, rerender } =
-      await renderHookToSnapshotStream(
-        ({ id }) => useLazyQuery(query, { variables: { id } }),
-        {
-          initialProps: { id: "1" },
-          wrapper: ({ children }) => (
-            <MockedProvider mocks={mocks}>{children}</MockedProvider>
-          ),
-        }
-      );
-
-    {
-      const [, result] = await takeSnapshot();
-
-      expect(result).toEqualQueryResult({
-        data: undefined,
-        error: undefined,
-        called: false,
-        loading: false,
-        networkStatus: NetworkStatus.ready,
-        previousData: undefined,
-        variables: { id: "1" },
-      });
-    }
-
-    await rerender({ id: "2" });
-
-    {
-      const [, result] = await takeSnapshot();
-
-      expect(result).toEqualQueryResult({
-        data: undefined,
-        error: undefined,
-        called: false,
-        loading: false,
-        networkStatus: NetworkStatus.ready,
-        previousData: undefined,
-        variables: { id: "1" },
-      });
-    }
-
-    const [execute] = getCurrentSnapshot();
-    const promise = execute();
-
-    {
-      const [, result] = await takeSnapshot();
-
-      expect(result).toEqualQueryResult({
-        data: undefined,
-        called: true,
-        loading: true,
-        networkStatus: NetworkStatus.loading,
-        previousData: undefined,
-        variables: { id: "2" },
-      });
-    }
-
-    {
-      const [, result] = await takeSnapshot();
-
-      expect(result).toEqualQueryResult({
-        data: mocks[1].result.data,
-        called: true,
-        loading: false,
-        networkStatus: NetworkStatus.ready,
-        previousData: undefined,
-        variables: { id: "2" },
-      });
-    }
-
-    await expect(promise).resolves.toEqualQueryResult({
-      data: mocks[1].result.data,
-      called: true,
-      loading: false,
-      networkStatus: NetworkStatus.ready,
-      previousData: undefined,
-      variables: { id: "2" },
-    });
-  });
-
   // https://github.com/apollographql/apollo-client/issues/10198
   it("uses the most recent query document when the hook rerenders before execution", async () => {
     const query = gql`
@@ -1908,9 +2200,8 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
@@ -1924,9 +2215,8 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
@@ -1937,25 +2227,17 @@ describe("useLazyQuery Hook", () => {
 
     const [execute] = getCurrentSnapshot();
 
-    const promise = execute();
-
-    {
-      const [, result] = await takeSnapshot();
-
-      expect(result).toEqualQueryResult({
-        data: undefined,
-        called: true,
-        loading: true,
-        networkStatus: NetworkStatus.loading,
-        previousData: undefined,
-        variables: {},
-      });
-    }
+    await expect(execute()).resolves.toEqualApolloQueryResult({
+      data: { hello: "Greetings" },
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      partial: false,
+    });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { hello: "Greetings" },
         called: true,
         loading: false,
@@ -1965,14 +2247,7 @@ describe("useLazyQuery Hook", () => {
       });
     }
 
-    await expect(promise).resolves.toEqualQueryResult({
-      data: { hello: "Greetings" },
-      called: true,
-      loading: false,
-      networkStatus: NetworkStatus.ready,
-      previousData: undefined,
-      variables: {},
-    });
+    await expect(takeSnapshot).not.toRerender();
   });
 
   it("does not refetch when rerendering after executing query", async () => {
@@ -2009,15 +2284,11 @@ describe("useLazyQuery Hook", () => {
 
     const client = new ApolloClient({ link, cache: new InMemoryCache() });
 
-    const { result, rerender } = renderHook(
-      () => useLazyQuery(query, { variables: { id: "1" } }),
-      {
-        initialProps: { id: "1" },
-        wrapper: ({ children }) => (
-          <ApolloProvider client={client}>{children}</ApolloProvider>
-        ),
-      }
-    );
+    const { result, rerender } = renderHook(() => useLazyQuery(query), {
+      wrapper: ({ children }) => (
+        <ApolloProvider client={client}>{children}</ApolloProvider>
+      ),
+    });
 
     const [execute] = result.current;
 
@@ -2093,7 +2364,7 @@ describe("useLazyQuery Hook", () => {
       expect(fetchCount).toBe(1);
 
       await waitFor(() => {
-        expect(result.current[1]).toEqualQueryResult({
+        expect(result.current[1]).toEqualLazyQueryResult({
           data: { user: { id: "2", name: "John Doe" } },
           called: true,
           loading: false,
@@ -2108,7 +2379,7 @@ describe("useLazyQuery Hook", () => {
       await act(() => result.current[0]());
 
       await waitFor(() => {
-        expect(result.current[1]).toEqualQueryResult({
+        expect(result.current[1]).toEqualLazyQueryResult({
           data: { user: { id: null, name: "John Default" } },
           called: true,
           loading: false,
@@ -2148,7 +2419,7 @@ describe("useLazyQuery Hook", () => {
       },
       {
         request: { query, variables: { id: "2" } },
-        result: { errors: [new GraphQLError("Oops")] },
+        result: { errors: [{ message: "Oops" }] },
         delay: 20,
       },
       {
@@ -2173,7 +2444,6 @@ describe("useLazyQuery Hook", () => {
 
           return useLazyQuery(query, {
             fetchPolicy: "cache-first",
-            variables: { id: "1" },
             skipPollAttempt: () => {
               trackClosureValue("skipPollAttempt", count);
               return false;
@@ -2194,14 +2464,14 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
         previousData: undefined,
-        variables: { id: "1" },
+        // @ts-expect-error Need to fix the return value of this property
+        variables: {},
       });
     }
 
@@ -2214,39 +2484,26 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
         previousData: undefined,
-        variables: { id: "1" },
+        // @ts-expect-error Need to fix the return value of this property
+        variables: {},
       });
     }
 
     let [execute] = getCurrentSnapshot();
     expect(execute).toBe(originalExecute);
 
-    await execute();
-
-    {
-      const [, result] = await takeSnapshot();
-
-      expect(result).toEqualQueryResult({
-        data: undefined,
-        called: true,
-        loading: true,
-        networkStatus: NetworkStatus.loading,
-        previousData: undefined,
-        variables: { id: "1" },
-      });
-    }
+    await execute({ variables: { id: "1" } });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { user: { id: "1", name: "John Doe" } },
         called: true,
         loading: false,
@@ -2265,15 +2522,10 @@ describe("useLazyQuery Hook", () => {
     // TODO: Update when https://github.com/testing-library/react-render-stream-testing-library/issues/13 is fixed
     await rerender(undefined);
 
-    [execute] = getCurrentSnapshot();
-    expect(execute).toBe(originalExecute);
-
-    await execute({ variables: { id: "2" } });
-
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { user: { id: "1", name: "John Doe" } },
         called: true,
         loading: false,
@@ -2283,29 +2535,25 @@ describe("useLazyQuery Hook", () => {
       });
     }
 
-    {
-      const [, result] = await takeSnapshot();
+    [execute] = getCurrentSnapshot();
+    expect(execute).toBe(originalExecute);
 
-      expect(result).toEqualQueryResult({
-        data: undefined,
-        called: true,
-        loading: true,
-        networkStatus: NetworkStatus.setVariables,
-        previousData: { user: { id: "1", name: "John Doe" } },
-        variables: { id: "2" },
-      });
-    }
+    await expect(execute({ variables: { id: "2" } })).rejects.toEqual(
+      new ApolloError({
+        graphQLErrors: [{ message: "Oops" }],
+      })
+    );
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: undefined,
+      expect(result).toEqualLazyQueryResult({
+        data: { user: { id: "1", name: "John Doe" } },
         error: new ApolloError({ graphQLErrors: [{ message: "Oops" }] }),
         called: true,
         loading: false,
         networkStatus: NetworkStatus.error,
-        previousData: { user: { id: "1", name: "John Doe" } },
+        previousData: undefined,
         variables: { id: "2" },
       });
     }
@@ -2323,39 +2571,26 @@ describe("useLazyQuery Hook", () => {
     [execute] = getCurrentSnapshot();
     expect(execute).toBe(originalExecute);
 
-    await execute({ variables: { id: "3" } });
-
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
-        data: undefined,
+      expect(result).toEqualLazyQueryResult({
+        data: { user: { id: "1", name: "John Doe" } },
         error: new ApolloError({ graphQLErrors: [{ message: "Oops" }] }),
         called: true,
         loading: false,
         networkStatus: NetworkStatus.error,
-        previousData: { user: { id: "1", name: "John Doe" } },
+        previousData: undefined,
         variables: { id: "2" },
       });
     }
 
-    {
-      const [, result] = await takeSnapshot();
-
-      expect(result).toEqualQueryResult({
-        data: undefined,
-        called: true,
-        loading: true,
-        networkStatus: NetworkStatus.setVariables,
-        previousData: { user: { id: "1", name: "John Doe" } },
-        variables: { id: "3" },
-      });
-    }
+    await execute({ variables: { id: "3" } });
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: { user: { id: "3", name: "Johnny Three" } },
         called: true,
         loading: false,
@@ -2411,9 +2646,10 @@ describe("useLazyQuery Hook", () => {
     const client = new ApolloClient({ link, cache: new InMemoryCache() });
 
     const { result, rerender } = renderHook(
-      ({ id }) => useLazyQuery(query, { variables: { id } }),
+      ({ notifyOnNetworkStatusChange }) =>
+        useLazyQuery(query, { notifyOnNetworkStatusChange }),
       {
-        initialProps: { id: "1" },
+        initialProps: { notifyOnNetworkStatusChange: false },
         wrapper: ({ children }) => (
           <ApolloProvider client={client}>{children}</ApolloProvider>
         ),
@@ -2422,13 +2658,15 @@ describe("useLazyQuery Hook", () => {
 
     const [execute] = result.current;
 
-    rerender({ id: "2" });
+    rerender({ notifyOnNetworkStatusChange: true });
 
     expect(result.current[0]).toBe(execute);
   });
 
   describe("network errors", () => {
-    async function check(errorPolicy: ErrorPolicy) {
+    // For errorPolicy:"none", we expect result.error to be defined and
+    // result.data to be undefined
+    it('handles errorPolicy:"none" appropriately', async () => {
       const networkError = new Error("from the network");
 
       const client = new ApolloClient({
@@ -2448,7 +2686,7 @@ describe("useLazyQuery Hook", () => {
         await renderHookToSnapshotStream(
           () =>
             useLazyQuery(helloQuery, {
-              errorPolicy,
+              errorPolicy: "none",
             }),
           {
             wrapper: ({ children }) => (
@@ -2460,9 +2698,8 @@ describe("useLazyQuery Hook", () => {
       {
         const [, result] = await takeSnapshot();
 
-        expect(result).toEqualQueryResult({
+        expect(result).toEqualLazyQueryResult({
           data: undefined,
-          error: undefined,
           called: false,
           loading: false,
           networkStatus: NetworkStatus.ready,
@@ -2471,25 +2708,16 @@ describe("useLazyQuery Hook", () => {
         });
       }
 
-      const execute = getCurrentSnapshot()[0];
-      setTimeout(execute);
+      const [execute] = getCurrentSnapshot();
 
-      {
-        const [, result] = await takeSnapshot();
+      await expect(execute()).rejects.toEqual(
+        new ApolloError({ networkError })
+      );
 
-        expect(result).toEqualQueryResult({
-          data: undefined,
-          called: true,
-          loading: true,
-          networkStatus: NetworkStatus.loading,
-          previousData: undefined,
-          variables: {},
-        });
-      }
       {
         const [, result] = await takeSnapshot();
 
-        expect(result).toEqualQueryResult({
+        expect(result).toEqualLazyQueryResult({
           data: undefined,
           error: new ApolloError({ networkError }),
           called: true,
@@ -2499,72 +2727,35 @@ describe("useLazyQuery Hook", () => {
           variables: {},
         });
       }
-    }
 
-    // For errorPolicy:"none", we expect result.error to be defined and
-    // result.data to be undefined, which is what we test above.
-    it('handles errorPolicy:"none" appropriately', () => check("none"));
+      await expect(takeSnapshot).not.toRerender();
+    });
 
     // If there was any data to report, errorPolicy:"all" would report both
     // result.data and result.error, but there is no GraphQL data when we
     // encounter a network error, so the test again captures desired behavior.
-    it('handles errorPolicy:"all" appropriately', () => check("all"));
-
-    // Technically errorPolicy:"ignore" is supposed to throw away result.error,
-    // but in the case of network errors, since there's no actual data to
-    // report, it's useful/important that we report result.error anyway.
-    it('handles errorPolicy:"ignore" appropriately', () => check("ignore"));
-  });
-
-  describe("options.defaultOptions", () => {
-    it("defaultOptions do not confuse useLazyQuery", async () => {
-      const counterQuery: TypedDocumentNode<{
-        counter: number;
-      }> = gql`
-        query GetCounter {
-          counter
-        }
-      `;
+    it('handles errorPolicy:"all" appropriately', async () => {
+      const networkError = new Error("from the network");
 
-      let count = 0;
       const client = new ApolloClient({
         cache: new InMemoryCache(),
         link: new ApolloLink(
           (request) =>
             new Observable((observer) => {
-              if (request.operationName === "GetCounter") {
-                setTimeout(() => {
-                  observer.next({
-                    data: {
-                      counter: ++count,
-                    },
-                  });
-                  observer.complete();
-                }, 20);
-              } else {
-                observer.error(
-                  new Error(
-                    `Unknown query: ${request.operationName || request.query}`
-                  )
-                );
-              }
+              setTimeout(() => {
+                observer.error(networkError);
+              }, 20);
             })
         ),
       });
 
-      const defaultFetchPolicy = "network-only";
-
       using _disabledAct = disableActEnvironment();
       const { takeSnapshot, getCurrentSnapshot } =
         await renderHookToSnapshotStream(
-          () => {
-            return useLazyQuery(counterQuery, {
-              defaultOptions: {
-                fetchPolicy: defaultFetchPolicy,
-                notifyOnNetworkStatusChange: true,
-              },
-            });
-          },
+          () =>
+            useLazyQuery(helloQuery, {
+              errorPolicy: "all",
+            }),
           {
             wrapper: ({ children }) => (
               <ApolloProvider client={client}>{children}</ApolloProvider>
@@ -2575,9 +2766,8 @@ describe("useLazyQuery Hook", () => {
       {
         const [, result] = await takeSnapshot();
 
-        expect(result).toEqualQueryResult({
+        expect(result).toEqualLazyQueryResult({
           data: undefined,
-          error: undefined,
           called: false,
           loading: false,
           networkStatus: NetworkStatus.ready,
@@ -2587,45 +2777,94 @@ describe("useLazyQuery Hook", () => {
       }
 
       const [execute] = getCurrentSnapshot();
-      const execResult = await execute();
 
-      expect(execResult).toEqualQueryResult({
-        data: { counter: 1 },
-        called: true,
-        loading: false,
-        networkStatus: NetworkStatus.ready,
-        previousData: undefined,
-        variables: {},
-      });
+      await expect(execute()).rejects.toEqual(
+        new ApolloError({ networkError })
+      );
 
       {
         const [, result] = await takeSnapshot();
 
-        expect(result).toEqualQueryResult({
+        expect(result).toEqualLazyQueryResult({
           data: undefined,
+          error: new ApolloError({ networkError }),
           called: true,
-          loading: true,
-          networkStatus: NetworkStatus.loading,
+          loading: false,
+          networkStatus: NetworkStatus.error,
+          previousData: undefined,
+          variables: {},
+        });
+      }
+
+      await expect(takeSnapshot).not.toRerender();
+    });
+
+    // Technically errorPolicy:"ignore" is supposed to throw away result.error,
+    // but in the case of network errors, since there's no actual data to
+    // report, it's useful/important that we report result.error anyway.
+    it('handles errorPolicy:"ignore" appropriately', async () => {
+      const networkError = new Error("from the network");
+
+      const client = new ApolloClient({
+        cache: new InMemoryCache(),
+        link: new ApolloLink(
+          (request) =>
+            new Observable((observer) => {
+              setTimeout(() => {
+                observer.error(networkError);
+              }, 20);
+            })
+        ),
+      });
+
+      using _disabledAct = disableActEnvironment();
+      const { takeSnapshot, getCurrentSnapshot } =
+        await renderHookToSnapshotStream(
+          () =>
+            useLazyQuery(helloQuery, {
+              errorPolicy: "ignore",
+            }),
+          {
+            wrapper: ({ children }) => (
+              <ApolloProvider client={client}>{children}</ApolloProvider>
+            ),
+          }
+        );
+
+      {
+        const [, result] = await takeSnapshot();
+
+        expect(result).toEqualLazyQueryResult({
+          data: undefined,
+          called: false,
+          loading: false,
+          networkStatus: NetworkStatus.ready,
           previousData: undefined,
           variables: {},
         });
       }
 
+      const [execute] = getCurrentSnapshot();
+
+      await expect(execute()).rejects.toEqual(
+        new ApolloError({ networkError })
+      );
+
       {
         const [, result] = await takeSnapshot();
 
-        expect(result).toEqualQueryResult({
-          data: { counter: 1 },
+        expect(result).toEqualLazyQueryResult({
+          data: undefined,
+          error: new ApolloError({ networkError }),
           called: true,
           loading: false,
-          networkStatus: NetworkStatus.ready,
+          networkStatus: NetworkStatus.error,
           previousData: undefined,
           variables: {},
         });
       }
 
-      const { options } = getCurrentSnapshot()[1].observable;
-      expect(options.fetchPolicy).toBe(defaultFetchPolicy);
+      await expect(takeSnapshot).not.toRerender();
     });
   });
 
@@ -2649,9 +2888,8 @@ describe("useLazyQuery Hook", () => {
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
@@ -2665,39 +2903,18 @@ describe("useLazyQuery Hook", () => {
     const promise = execute();
     expect(requests).toBe(1);
 
-    {
-      const [, result] = await takeSnapshot();
-
-      expect(result).toEqualQueryResult({
-        data: undefined,
-        called: true,
-        loading: true,
-        networkStatus: NetworkStatus.loading,
-        previousData: undefined,
-        variables: {},
-      });
-    }
-
     await client.clearStore();
 
-    await expect(promise).resolves.toEqualQueryResult({
-      data: undefined,
-      error: new ApolloError({
-        networkError: new InvariantError(
-          "Store reset while query was in flight (not completed in link chain)"
-        ),
-      }),
-      loading: true,
-      networkStatus: NetworkStatus.loading,
-      called: true,
-      previousData: undefined,
-      variables: {},
-    });
+    await expect(promise).rejects.toEqual(
+      new InvariantError(
+        "Store reset while query was in flight (not completed in link chain)"
+      )
+    );
 
     {
       const [, result] = await takeSnapshot();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualLazyQueryResult({
         data: undefined,
         error: new ApolloError({
           networkError: new InvariantError(
@@ -2783,7 +3000,7 @@ describe("useLazyQuery Hook", () => {
       const [execute] = getCurrentSnapshot();
       const result = await execute();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualApolloQueryResult({
         data: {
           currentUser: {
             __typename: "User",
@@ -2791,20 +3008,15 @@ describe("useLazyQuery Hook", () => {
             name: "Test User",
           },
         },
-        called: true,
         loading: false,
         networkStatus: NetworkStatus.ready,
-        previousData: undefined,
-        variables: {},
+        partial: false,
       });
 
-      // Loading
-      await takeSnapshot();
-
       {
         const [, result] = await takeSnapshot();
 
-        expect(result).toEqualQueryResult({
+        expect(result).toEqualLazyQueryResult({
           data: {
             currentUser: {
               __typename: "User",
@@ -2819,6 +3031,8 @@ describe("useLazyQuery Hook", () => {
           variables: {},
         });
       }
+
+      await expect(takeSnapshot).not.toRerender();
     });
 
     it("does not mask queries when dataMasking is `false`", async () => {
@@ -2889,7 +3103,7 @@ describe("useLazyQuery Hook", () => {
       const [execute] = getCurrentSnapshot();
       const result = await execute();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualApolloQueryResult({
         data: {
           currentUser: {
             __typename: "User",
@@ -2898,20 +3112,15 @@ describe("useLazyQuery Hook", () => {
             age: 30,
           },
         },
-        called: true,
         loading: false,
         networkStatus: NetworkStatus.ready,
-        previousData: undefined,
-        variables: {},
+        partial: false,
       });
 
-      // Loading
-      await takeSnapshot();
-
       {
         const [, result] = await takeSnapshot();
 
-        expect(result).toEqualQueryResult({
+        expect(result).toEqualLazyQueryResult({
           data: {
             currentUser: {
               __typename: "User",
@@ -2927,6 +3136,8 @@ describe("useLazyQuery Hook", () => {
           variables: {},
         });
       }
+
+      await expect(takeSnapshot).not.toRerender();
     });
 
     it("does not mask queries by default", async () => {
@@ -2996,7 +3207,7 @@ describe("useLazyQuery Hook", () => {
       const [execute] = getCurrentSnapshot();
       const result = await execute();
 
-      expect(result).toEqualQueryResult({
+      expect(result).toEqualApolloQueryResult({
         data: {
           currentUser: {
             __typename: "User",
@@ -3005,20 +3216,15 @@ describe("useLazyQuery Hook", () => {
             age: 30,
           },
         },
-        called: true,
         loading: false,
         networkStatus: NetworkStatus.ready,
-        previousData: undefined,
-        variables: {},
+        partial: false,
       });
 
-      // Loading
-      await takeSnapshot();
-
       {
         const [, result] = await takeSnapshot();
 
-        expect(result).toEqualQueryResult({
+        expect(result).toEqualLazyQueryResult({
           data: {
             currentUser: {
               __typename: "User",
@@ -3034,6 +3240,8 @@ describe("useLazyQuery Hook", () => {
           variables: {},
         });
       }
+
+      await expect(takeSnapshot).not.toRerender();
     });
 
     it("masks queries updated by the cache", async () => {
@@ -3098,188 +3306,1983 @@ describe("useLazyQuery Hook", () => {
       // initial render
       await takeSnapshot();
 
-      const [execute] = getCurrentSnapshot();
-      await execute();
+      const [execute] = getCurrentSnapshot();
+      await execute();
+
+      {
+        const [, result] = await takeSnapshot();
+
+        expect(result).toEqualLazyQueryResult({
+          data: {
+            currentUser: {
+              __typename: "User",
+              id: 1,
+              name: "Test User",
+            },
+          },
+          called: true,
+          loading: false,
+          networkStatus: NetworkStatus.ready,
+          previousData: undefined,
+          variables: {},
+        });
+      }
+
+      client.writeQuery({
+        query,
+        data: {
+          currentUser: {
+            __typename: "User",
+            id: 1,
+            name: "Test User (updated)",
+            age: 35,
+          },
+        },
+      });
+
+      {
+        const [, result] = await takeSnapshot();
+
+        expect(result).toEqualLazyQueryResult({
+          data: {
+            currentUser: {
+              __typename: "User",
+              id: 1,
+              name: "Test User (updated)",
+            },
+          },
+          called: true,
+          loading: false,
+          networkStatus: NetworkStatus.ready,
+          previousData: {
+            currentUser: { __typename: "User", id: 1, name: "Test User" },
+          },
+          variables: {},
+        });
+      }
+
+      await expect(takeSnapshot).not.toRerender();
+    });
+
+    it("does not rerender when updating field in named fragment", async () => {
+      type UserFieldsFragment = {
+        __typename: "User";
+        age: number;
+      } & { " $fragmentName"?: "UserFieldsFragment" };
+
+      interface Query {
+        currentUser: {
+          __typename: "User";
+          id: number;
+          name: string;
+        } & { " $fragmentRefs"?: { UserFieldsFragment: UserFieldsFragment } };
+      }
+
+      const query: TypedDocumentNode<Query, Record<string, never>> = gql`
+        query MaskedQuery {
+          currentUser {
+            id
+            name
+            ...UserFields
+          }
+        }
+
+        fragment UserFields on User {
+          age
+        }
+      `;
+
+      const mocks = [
+        {
+          request: { query },
+          result: {
+            data: {
+              currentUser: {
+                __typename: "User",
+                id: 1,
+                name: "Test User",
+                age: 30,
+              },
+            },
+          },
+          delay: 20,
+        },
+      ];
+
+      const client = new ApolloClient({
+        dataMasking: true,
+        cache: new InMemoryCache(),
+        link: new MockLink(mocks),
+      });
+
+      using _disabledAct = disableActEnvironment();
+      const { takeSnapshot, getCurrentSnapshot } =
+        await renderHookToSnapshotStream(() => useLazyQuery(query), {
+          wrapper: ({ children }) => (
+            <ApolloProvider client={client}>{children}</ApolloProvider>
+          ),
+        });
+
+      // initial render
+      await takeSnapshot();
+
+      const [execute] = getCurrentSnapshot();
+      await execute();
+
+      {
+        const [, result] = await takeSnapshot();
+
+        expect(result).toEqualLazyQueryResult({
+          data: {
+            currentUser: {
+              __typename: "User",
+              id: 1,
+              name: "Test User",
+            },
+          },
+          called: true,
+          loading: false,
+          networkStatus: NetworkStatus.ready,
+          previousData: undefined,
+          variables: {},
+        });
+      }
+
+      client.writeQuery({
+        query,
+        data: {
+          currentUser: {
+            __typename: "User",
+            id: 1,
+            name: "Test User",
+            age: 35,
+          },
+        },
+      });
+
+      await expect(takeSnapshot).not.toRerender();
+
+      expect(client.readQuery({ query })).toEqual({
+        currentUser: {
+          __typename: "User",
+          id: 1,
+          name: "Test User",
+          age: 35,
+        },
+      });
+    });
+  });
+});
+
+test("throws when calling `refetch` before execute function is called", async () => {
+  const { query, mocks } = setupSimpleCase();
+
+  const client = new ApolloClient({
+    cache: new InMemoryCache(),
+    link: new MockLink(mocks),
+  });
+
+  using _disabledAct = disableActEnvironment();
+  const { takeSnapshot } = await renderHookToSnapshotStream(
+    () => useLazyQuery(query),
+    {
+      wrapper: ({ children }) => (
+        <ApolloProvider client={client}>{children}</ApolloProvider>
+      ),
+    }
+  );
+
+  const [, { refetch }] = await takeSnapshot();
+
+  expect(() => {
+    void refetch();
+  }).toThrow(
+    new InvariantError(
+      "useLazyQuery: 'refetch' cannot be called before executing the query."
+    )
+  );
+});
+
+test("throws when calling `fetchMore` before execute function is called", async () => {
+  const { query, mocks } = setupSimpleCase();
+
+  const client = new ApolloClient({
+    cache: new InMemoryCache(),
+    link: new MockLink(mocks),
+  });
+
+  using _disabledAct = disableActEnvironment();
+  const { takeSnapshot } = await renderHookToSnapshotStream(
+    () => useLazyQuery(query),
+    {
+      wrapper: ({ children }) => (
+        <ApolloProvider client={client}>{children}</ApolloProvider>
+      ),
+    }
+  );
+
+  const [, { fetchMore }] = await takeSnapshot();
+
+  expect(() => {
+    void fetchMore({});
+  }).toThrow(
+    new InvariantError(
+      "useLazyQuery: 'fetchMore' cannot be called before executing the query."
+    )
+  );
+});
+
+test("throws when calling `subscribeToMore` before execute function is called", async () => {
+  const { query, mocks } = setupSimpleCase();
+
+  const client = new ApolloClient({
+    cache: new InMemoryCache(),
+    link: new MockLink(mocks),
+  });
+
+  using _disabledAct = disableActEnvironment();
+  const { takeSnapshot } = await renderHookToSnapshotStream(
+    () => useLazyQuery(query),
+    {
+      wrapper: ({ children }) => (
+        <ApolloProvider client={client}>{children}</ApolloProvider>
+      ),
+    }
+  );
+
+  const [, { subscribeToMore }] = await takeSnapshot();
+
+  expect(() => {
+    subscribeToMore({
+      document: gql`
+        subscription {
+          foo
+        }
+      `,
+    });
+  }).toThrow(
+    new InvariantError(
+      "useLazyQuery: 'subscribeToMore' cannot be called before executing the query."
+    )
+  );
+});
+
+test("throws when calling `updateQuery` before execute function is called", async () => {
+  const { query, mocks } = setupSimpleCase();
+
+  const client = new ApolloClient({
+    cache: new InMemoryCache(),
+    link: new MockLink(mocks),
+  });
+
+  using _disabledAct = disableActEnvironment();
+  const { takeSnapshot } = await renderHookToSnapshotStream(
+    () => useLazyQuery(query),
+    {
+      wrapper: ({ children }) => (
+        <ApolloProvider client={client}>{children}</ApolloProvider>
+      ),
+    }
+  );
+
+  const [, { updateQuery }] = await takeSnapshot();
+
+  expect(() => {
+    updateQuery(() => ({ greeting: "foo" }));
+  }).toThrow(
+    new InvariantError(
+      "useLazyQuery: 'updateQuery' cannot be called before executing the query."
+    )
+  );
+});
+
+test("throws when calling `startPolling` before execute function is called", async () => {
+  const { query, mocks } = setupSimpleCase();
+
+  const client = new ApolloClient({
+    cache: new InMemoryCache(),
+    link: new MockLink(mocks),
+  });
+
+  using _disabledAct = disableActEnvironment();
+  const { takeSnapshot } = await renderHookToSnapshotStream(
+    () => useLazyQuery(query),
+    {
+      wrapper: ({ children }) => (
+        <ApolloProvider client={client}>{children}</ApolloProvider>
+      ),
+    }
+  );
+
+  const [, { startPolling }] = await takeSnapshot();
+
+  expect(() => {
+    startPolling(10);
+  }).toThrow(
+    new InvariantError(
+      "useLazyQuery: 'startPolling' cannot be called before executing the query."
+    )
+  );
+});
+
+test("throws when calling `stopPolling` before execute function is called", async () => {
+  const { query, mocks } = setupSimpleCase();
+
+  const client = new ApolloClient({
+    cache: new InMemoryCache(),
+    link: new MockLink(mocks),
+  });
+
+  using _disabledAct = disableActEnvironment();
+  const { takeSnapshot } = await renderHookToSnapshotStream(
+    () => useLazyQuery(query),
+    {
+      wrapper: ({ children }) => (
+        <ApolloProvider client={client}>{children}</ApolloProvider>
+      ),
+    }
+  );
+
+  const [, { stopPolling }] = await takeSnapshot();
+
+  expect(() => {
+    stopPolling();
+  }).toThrow(
+    new InvariantError(
+      "useLazyQuery: 'stopPolling' cannot be called before executing the query."
+    )
+  );
+});
+
+test("throws when calling execute function during first render", async () => {
+  using _consoleSpy = spyOnConsole("error");
+  const { query, mocks } = setupSimpleCase();
+
+  function App() {
+    const [execute] = useLazyQuery(query);
+
+    void execute();
+
+    return null;
+  }
+
+  // We need to use the `async` function here to prevent console errors from
+  // showing up
+  await expect(async () =>
+    renderAsync(<App />, {
+      wrapper: ({ children }) => (
+        <MockedProvider mocks={mocks}>{children}</MockedProvider>
+      ),
+    })
+  ).rejects.toThrow(
+    new InvariantError(
+      "useLazyQuery: 'execute' should not be called during render. To start a query during render, use the 'useQuery' hook."
+    )
+  );
+});
+
+test("throws when calling execute function during subsequent render", async () => {
+  using _consoleSpy = spyOnConsole("error");
+  const { query, mocks } = setupSimpleCase();
+  const user = userEvent.setup();
+
+  function App() {
+    const [count, setCount] = React.useState(0);
+    const [execute] = useLazyQuery(query);
+
+    if (count === 1) {
+      void execute();
+    }
+
+    return <button onClick={() => setCount(1)}>Load</button>;
+  }
+
+  let error!: Error;
+
+  await renderAsync(<App />, {
+    wrapper: ({ children }) => (
+      <ErrorBoundary onError={(e) => (error = e)} fallback={<div>Oops</div>}>
+        <MockedProvider mocks={mocks}>{children}</MockedProvider>
+      </ErrorBoundary>
+    ),
+  });
+
+  await act(() => user.click(screen.getByText("Load")));
+
+  expect(error).toEqual(
+    new InvariantError(
+      "useLazyQuery: 'execute' should not be called during render. To start a query during render, use the 'useQuery' hook."
+    )
+  );
+});
+
+test("uses the updated client when executing the function after changing clients", async () => {
+  const { query } = setupSimpleCase();
+
+  const client1 = new ApolloClient({
+    cache: new InMemoryCache(),
+    link: new MockLink([
+      {
+        request: { query },
+        result: { data: { greeting: "Hello client 1" } },
+        delay: 20,
+      },
+    ]),
+  });
+
+  const client2 = new ApolloClient({
+    cache: new InMemoryCache(),
+    link: new MockLink([
+      {
+        request: { query },
+        result: { data: { greeting: "Hello client 2" } },
+        delay: 20,
+      },
+    ]),
+  });
+
+  using _disabledAct = disableActEnvironment();
+  const { takeSnapshot, getCurrentSnapshot, rerender } =
+    await renderHookToSnapshotStream(
+      ({ client }) => useLazyQuery(query, { client }),
+      { initialProps: { client: client1 } }
+    );
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: undefined,
+      called: false,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: {},
+    });
+  }
+
+  const [execute] = getCurrentSnapshot();
+
+  await expect(execute()).resolves.toEqualApolloQueryResult({
+    data: { greeting: "Hello client 1" },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: { greeting: "Hello client 1" },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: {},
+    });
+  }
+
+  await rerender({ client: client2 });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: { greeting: "Hello client 1" },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: {},
+    });
+  }
+
+  await expect(execute()).resolves.toEqualApolloQueryResult({
+    data: { greeting: "Hello client 2" },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: { greeting: "Hello client 2" },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: { greeting: "Hello client 1" },
+      variables: {},
+    });
+  }
+
+  await expect(takeSnapshot).not.toRerender();
+});
+
+test("responds to cache updates after executing query", async () => {
+  const { query } = setupSimpleCase();
+
+  const client = new ApolloClient({
+    cache: new InMemoryCache(),
+    link: new MockLink([
+      {
+        request: { query },
+        result: { data: { greeting: "Hello" } },
+        delay: 20,
+      },
+    ]),
+  });
+
+  using _disabledAct = disableActEnvironment();
+  const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
+    () => useLazyQuery(query),
+    {
+      wrapper: ({ children }) => (
+        <ApolloProvider client={client}>{children}</ApolloProvider>
+      ),
+    }
+  );
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: undefined,
+      called: false,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: {},
+    });
+  }
+
+  const [execute] = getCurrentSnapshot();
+
+  await expect(execute()).resolves.toEqualApolloQueryResult({
+    data: { greeting: "Hello" },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: { greeting: "Hello" },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: {},
+    });
+  }
+
+  client.writeQuery({
+    query,
+    data: {
+      greeting: "Hello (updated)",
+    },
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: { greeting: "Hello (updated)" },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: {
+        greeting: "Hello",
+      },
+      variables: {},
+    });
+  }
+
+  await expect(takeSnapshot).not.toRerender();
+});
+
+test("responds to cache updates after changing variables", async () => {
+  const { query, mocks } = setupVariablesCase();
+
+  const client = new ApolloClient({
+    cache: new InMemoryCache(),
+    link: new MockLink(mocks),
+  });
+
+  using _disabledAct = disableActEnvironment();
+  const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
+    () => useLazyQuery(query),
+    {
+      wrapper: ({ children }) => (
+        <ApolloProvider client={client}>{children}</ApolloProvider>
+      ),
+    }
+  );
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: undefined,
+      called: false,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      // @ts-expect-error this should be undefined
+      variables: {},
+    });
+  }
+
+  const [execute] = getCurrentSnapshot();
+
+  await expect(
+    execute({ variables: { id: "1" } })
+  ).resolves.toEqualApolloQueryResult({
+    data: {
+      character: { __typename: "Character", id: "1", name: "Spider-Man" },
+    },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: {
+        character: { __typename: "Character", id: "1", name: "Spider-Man" },
+      },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: { id: "1" },
+    });
+  }
+
+  await expect(
+    execute({ variables: { id: "2" } })
+  ).resolves.toEqualApolloQueryResult({
+    data: {
+      character: { __typename: "Character", id: "2", name: "Black Widow" },
+    },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: {
+        character: { __typename: "Character", id: "2", name: "Black Widow" },
+      },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: {
+        character: { __typename: "Character", id: "1", name: "Spider-Man" },
+      },
+      variables: { id: "2" },
+    });
+  }
+
+  client.writeQuery({
+    query,
+    variables: { id: "2" },
+    data: {
+      character: {
+        __typename: "Character",
+        id: "2",
+        name: "Black Widow (updated)",
+      },
+    },
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: {
+        character: {
+          __typename: "Character",
+          id: "2",
+          name: "Black Widow (updated)",
+        },
+      },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: {
+        character: { __typename: "Character", id: "2", name: "Black Widow" },
+      },
+      variables: { id: "2" },
+    });
+  }
+
+  await expect(takeSnapshot).not.toRerender();
+
+  // Ensure that writing data to a different set of variables does not rerender
+  // the hook
+  client.writeQuery({
+    query,
+    variables: { id: "1" },
+    data: {
+      character: {
+        __typename: "Character",
+        id: "1",
+        name: "Spider-Man (updated)",
+      },
+    },
+  });
+
+  await expect(takeSnapshot).not.toRerender();
+});
+
+test("uses cached result when switching to variables already written to the cache", async () => {
+  const { query, mocks } = setupVariablesCase();
+
+  const client = new ApolloClient({
+    cache: new InMemoryCache(),
+    link: new MockLink(mocks),
+  });
+
+  client.writeQuery({
+    query,
+    variables: { id: "2" },
+    data: {
+      character: { __typename: "Character", id: "2", name: "Cached Character" },
+    },
+  });
+
+  using _disabledAct = disableActEnvironment();
+  const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
+    () => useLazyQuery(query),
+    {
+      wrapper: ({ children }) => (
+        <ApolloProvider client={client}>{children}</ApolloProvider>
+      ),
+    }
+  );
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: undefined,
+      called: false,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      // @ts-expect-error this should be undefined
+      variables: {},
+    });
+  }
+
+  const [execute] = getCurrentSnapshot();
+
+  await expect(
+    execute({ variables: { id: "1" } })
+  ).resolves.toEqualApolloQueryResult({
+    data: {
+      character: { __typename: "Character", id: "1", name: "Spider-Man" },
+    },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: {
+        character: { __typename: "Character", id: "1", name: "Spider-Man" },
+      },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: { id: "1" },
+    });
+  }
+
+  await expect(
+    execute({ variables: { id: "2" } })
+  ).resolves.toEqualApolloQueryResult({
+    data: {
+      character: {
+        __typename: "Character",
+        id: "2",
+        name: "Cached Character",
+      },
+    },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: {
+        character: {
+          __typename: "Character",
+          id: "2",
+          name: "Cached Character",
+        },
+      },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: {
+        character: { __typename: "Character", id: "1", name: "Spider-Man" },
+      },
+      variables: { id: "2" },
+    });
+  }
+
+  await expect(takeSnapshot).not.toRerender();
+});
+
+test("renders loading states where necessary when switching to variables maybe written to the cache with notifyOnNetworkStatusChange", async () => {
+  const { query, mocks } = setupVariablesCase();
+
+  const client = new ApolloClient({
+    cache: new InMemoryCache(),
+    link: new MockLink(mocks),
+  });
+
+  client.writeQuery({
+    query,
+    variables: { id: "2" },
+    data: {
+      character: { __typename: "Character", id: "2", name: "Cached Character" },
+    },
+  });
+
+  using _disabledAct = disableActEnvironment();
+  const { takeSnapshot, getCurrentSnapshot } = await renderHookToSnapshotStream(
+    () => useLazyQuery(query, { notifyOnNetworkStatusChange: true }),
+    {
+      wrapper: ({ children }) => (
+        <ApolloProvider client={client}>{children}</ApolloProvider>
+      ),
+    }
+  );
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: undefined,
+      called: false,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      // @ts-expect-error this should be undefined
+      variables: {},
+    });
+  }
+
+  const [execute] = getCurrentSnapshot();
+
+  await expect(
+    execute({ variables: { id: "1" } })
+  ).resolves.toEqualApolloQueryResult({
+    data: {
+      character: { __typename: "Character", id: "1", name: "Spider-Man" },
+    },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: undefined,
+      called: true,
+      loading: true,
+      networkStatus: NetworkStatus.loading,
+      previousData: undefined,
+      variables: { id: "1" },
+    });
+  }
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: {
+        character: { __typename: "Character", id: "1", name: "Spider-Man" },
+      },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: { id: "1" },
+    });
+  }
+
+  await expect(
+    execute({ variables: { id: "2" } })
+  ).resolves.toEqualApolloQueryResult({
+    data: {
+      character: {
+        __typename: "Character",
+        id: "2",
+        name: "Cached Character",
+      },
+    },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: {
+        character: {
+          __typename: "Character",
+          id: "2",
+          name: "Cached Character",
+        },
+      },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: {
+        character: { __typename: "Character", id: "1", name: "Spider-Man" },
+      },
+      variables: { id: "2" },
+    });
+  }
+
+  await expect(
+    execute({ variables: { id: "3" } })
+  ).resolves.toEqualApolloQueryResult({
+    data: {
+      character: {
+        __typename: "Character",
+        id: "3",
+        name: "Iron Man",
+      },
+    },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: undefined,
+      called: true,
+      loading: true,
+      networkStatus: NetworkStatus.setVariables,
+      previousData: {
+        character: {
+          __typename: "Character",
+          id: "2",
+          name: "Cached Character",
+        },
+      },
+      variables: { id: "3" },
+    });
+  }
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: {
+        character: {
+          __typename: "Character",
+          id: "3",
+          name: "Iron Man",
+        },
+      },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: {
+        character: {
+          __typename: "Character",
+          id: "2",
+          name: "Cached Character",
+        },
+      },
+      variables: { id: "3" },
+    });
+  }
+
+  await expect(takeSnapshot).not.toRerender();
+});
+
+test("applies `errorPolicy` on next fetch when it changes between renders", async () => {
+  const query: TypedDocumentNode<
+    {
+      character: { __typename: "Character"; id: string; name: string } | null;
+    },
+    VariablesCaseVariables
+  > = gql`
+    query CharacterQuery($id: ID!) {
+      character(id: $id) {
+        id
+        name
+      }
+    }
+  `;
+
+  const mocks = [
+    {
+      request: { query, variables: { id: "1" } },
+      result: {
+        data: {
+          character: { __typename: "Character", id: "1", name: "Spider-Man" },
+        },
+      },
+      delay: 20,
+    },
+    {
+      request: { query, variables: { id: "1" } },
+      result: {
+        data: {
+          character: null,
+        },
+        errors: [new GraphQLError("Could not find character 1")],
+      },
+      delay: 20,
+    },
+  ];
+
+  const client = new ApolloClient({
+    cache: new InMemoryCache(),
+    link: new MockLink(mocks),
+  });
+
+  using _disabledAct = disableActEnvironment();
+  const { takeSnapshot, getCurrentSnapshot, rerender } =
+    await renderHookToSnapshotStream(
+      ({ errorPolicy }: { errorPolicy: ErrorPolicy }) =>
+        useLazyQuery(query, { errorPolicy }),
+      {
+        initialProps: { errorPolicy: "none" },
+        wrapper: ({ children }) => (
+          <ApolloProvider client={client}>{children}</ApolloProvider>
+        ),
+      }
+    );
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: undefined,
+      called: false,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      // @ts-expect-error this should be undefined
+      variables: {},
+    });
+  }
+
+  const [execute] = getCurrentSnapshot();
+
+  await expect(
+    execute({ variables: { id: "1" } })
+  ).resolves.toEqualApolloQueryResult({
+    data: {
+      character: { __typename: "Character", id: "1", name: "Spider-Man" },
+    },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: {
+        character: { __typename: "Character", id: "1", name: "Spider-Man" },
+      },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: { id: "1" },
+    });
+  }
+
+  await rerender({ errorPolicy: "all" });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: {
+        character: { __typename: "Character", id: "1", name: "Spider-Man" },
+      },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: { id: "1" },
+    });
+  }
+
+  const [, { refetch }] = getCurrentSnapshot();
+  void refetch();
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: {
+        character: null,
+      },
+      error: new ApolloError({
+        graphQLErrors: [{ message: "Could not find character 1" }],
+      }),
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.error,
+      previousData: {
+        character: { __typename: "Character", id: "1", name: "Spider-Man" },
+      },
+      variables: { id: "1" },
+    });
+  }
+
+  await expect(takeSnapshot).not.toRerender();
+});
+
+test("applies `context` on next fetch when it changes between renders", async () => {
+  const query = gql`
+    query {
+      context
+    }
+  `;
+
+  const client = new ApolloClient({
+    cache: new InMemoryCache(),
+    link: new ApolloLink((operation) => {
+      const context = operation.getContext();
+
+      return new Observable((observer) => {
+        setTimeout(() => {
+          observer.next({ data: { context: { source: context.source } } });
+          observer.complete();
+        }, 20);
+      });
+    }),
+  });
+
+  using _disabledAct = disableActEnvironment();
+  const { takeSnapshot, getCurrentSnapshot, rerender } =
+    await renderHookToSnapshotStream(
+      ({ context }) =>
+        useLazyQuery(query, { context, fetchPolicy: "network-only" }),
+      {
+        initialProps: { context: { source: "initialHookValue" } },
+        wrapper: ({ children }) => (
+          <ApolloProvider client={client}>{children}</ApolloProvider>
+        ),
+      }
+    );
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: undefined,
+      called: false,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: {},
+    });
+  }
+
+  const [execute] = getCurrentSnapshot();
+
+  await expect(execute()).resolves.toEqualApolloQueryResult({
+    data: { context: { source: "initialHookValue" } },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: { context: { source: "initialHookValue" } },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: {},
+    });
+  }
+
+  await rerender({ context: { source: "rerender" } });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: { context: { source: "initialHookValue" } },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: {},
+    });
+  }
+
+  await expect(execute()).resolves.toEqualApolloQueryResult({
+    data: { context: { source: "rerender" } },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: { context: { source: "rerender" } },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: { context: { source: "initialHookValue" } },
+      variables: {},
+    });
+  }
+
+  await rerender({ context: { source: "rerenderForRefetch" } });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: { context: { source: "rerender" } },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: { context: { source: "initialHookValue" } },
+      variables: {},
+    });
+  }
+
+  // Ensure context isn't just applied to execute function
+  void getCurrentSnapshot()[1].refetch();
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: { context: { source: "rerenderForRefetch" } },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: { context: { source: "rerender" } },
+      variables: {},
+    });
+  }
+
+  await expect(
+    execute({ context: { source: "execute" } })
+  ).resolves.toEqualApolloQueryResult({
+    data: { context: { source: "execute" } },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: { context: { source: "execute" } },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: { context: { source: "rerenderForRefetch" } },
+      variables: {},
+    });
+  }
+
+  await expect(takeSnapshot).not.toRerender();
+});
+
+test("applies `refetchWritePolicy` on next fetch when it changes between renders", async () => {
+  const query: TypedDocumentNode<
+    { primes: number[] },
+    { min: number; max: number }
+  > = gql`
+    query GetPrimes($min: number, $max: number) {
+      primes(min: $min, max: $max)
+    }
+  `;
+
+  const mocks = [
+    {
+      request: { query, variables: { min: 0, max: 12 } },
+      result: { data: { primes: [2, 3, 5, 7, 11] } },
+      delay: 20,
+    },
+    {
+      request: { query, variables: { min: 12, max: 30 } },
+      result: { data: { primes: [13, 17, 19, 23, 29] } },
+      delay: 10,
+    },
+    {
+      request: { query, variables: { min: 30, max: 50 } },
+      result: { data: { primes: [31, 37, 41, 43, 47] } },
+      delay: 10,
+    },
+  ];
+
+  const mergeParams: [number[] | undefined, number[]][] = [];
+  const cache = new InMemoryCache({
+    typePolicies: {
+      Query: {
+        fields: {
+          primes: {
+            keyArgs: false,
+            merge(existing: number[] | undefined, incoming: number[]) {
+              mergeParams.push([existing, incoming]);
+              return existing ? existing.concat(incoming) : incoming;
+            },
+          },
+        },
+      },
+    },
+  });
+
+  const client = new ApolloClient({
+    cache,
+    link: new MockLink(mocks),
+  });
+
+  using _disabledAct = disableActEnvironment();
+  const { takeSnapshot, getCurrentSnapshot, rerender } =
+    await renderHookToSnapshotStream(
+      ({ refetchWritePolicy }) => useLazyQuery(query, { refetchWritePolicy }),
+      {
+        initialProps: { refetchWritePolicy: "merge" as RefetchWritePolicy },
+        wrapper: ({ children }) => (
+          <ApolloProvider client={client}>{children}</ApolloProvider>
+        ),
+      }
+    );
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: undefined,
+      called: false,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      // @ts-expect-error needs to be undefined
+      variables: {},
+    });
+  }
+
+  const [execute] = getCurrentSnapshot();
+
+  await expect(
+    execute({ variables: { min: 0, max: 12 } })
+  ).resolves.toEqualApolloQueryResult({
+    data: mocks[0].result.data,
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: mocks[0].result.data,
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: { min: 0, max: 12 },
+    });
+  }
+
+  expect(mergeParams).toEqual([[undefined, [2, 3, 5, 7, 11]]]);
+
+  const [, { refetch }] = getCurrentSnapshot();
+
+  void refetch({ min: 12, max: 30 });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: { primes: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: mocks[0].result.data,
+      variables: { min: 12, max: 30 },
+    });
+  }
+
+  expect(mergeParams).toEqual([
+    [undefined, [2, 3, 5, 7, 11]],
+    [
+      [2, 3, 5, 7, 11],
+      [13, 17, 19, 23, 29],
+    ],
+  ]);
+
+  await rerender({ refetchWritePolicy: "overwrite" });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: { primes: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: mocks[0].result.data,
+      variables: { min: 12, max: 30 },
+    });
+  }
+
+  void refetch({ min: 30, max: 50 });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: mocks[2].result.data,
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: { primes: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29] },
+      variables: { min: 30, max: 50 },
+    });
+  }
+
+  expect(mergeParams).toEqual([
+    [undefined, [2, 3, 5, 7, 11]],
+    [
+      [2, 3, 5, 7, 11],
+      [13, 17, 19, 23, 29],
+    ],
+    [undefined, [31, 37, 41, 43, 47]],
+  ]);
+
+  await expect(takeSnapshot).not.toRerender();
+});
+
+test("applies `returnPartialData` on next fetch when it changes between renders", async () => {
+  const fullQuery = gql`
+    query ($id: ID!) {
+      character(id: $id) {
+        id
+        name
+      }
+    }
+  `;
+
+  const partialQuery = gql`
+    query ($id: ID!) {
+      character(id: $id) {
+        id
+      }
+    }
+  `;
+
+  const mocks = [
+    {
+      request: { query: fullQuery, variables: { id: "1" } },
+      result: {
+        data: {
+          character: {
+            __typename: "Character",
+            id: "1",
+            name: "Doctor Strange",
+          },
+        },
+      },
+      delay: 20,
+    },
+    {
+      request: { query: fullQuery, variables: { id: "2" } },
+      result: {
+        data: {
+          character: {
+            __typename: "Character",
+            id: "2",
+            name: "Hulk",
+          },
+        },
+      },
+      delay: 20,
+    },
+  ];
+
+  const client = new ApolloClient({
+    cache: new InMemoryCache(),
+    link: new MockLink(mocks),
+  });
+
+  client.writeQuery({
+    query: partialQuery,
+    data: { character: { __typename: "Character", id: "1" } },
+    variables: { id: "1" },
+  });
+
+  client.writeQuery({
+    query: partialQuery,
+    data: { character: { __typename: "Character", id: "2" } },
+    variables: { id: "2" },
+  });
+
+  using _disabledAct = disableActEnvironment();
+  const { takeSnapshot, getCurrentSnapshot, rerender } =
+    await renderHookToSnapshotStream(
+      ({ returnPartialData }) => useLazyQuery(fullQuery, { returnPartialData }),
+      {
+        initialProps: { returnPartialData: false },
+        wrapper: ({ children }) => (
+          <ApolloProvider client={client}>{children}</ApolloProvider>
+        ),
+      }
+    );
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: undefined,
+      called: false,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: {},
+    });
+  }
+
+  const [execute] = getCurrentSnapshot();
+
+  await expect(
+    execute({ variables: { id: "1" } })
+  ).resolves.toEqualApolloQueryResult({
+    data: {
+      character: { __typename: "Character", id: "1", name: "Doctor Strange" },
+    },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: {
+        character: { __typename: "Character", id: "1", name: "Doctor Strange" },
+      },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: { id: "1" },
+    });
+  }
+
+  await rerender({ returnPartialData: true });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: {
+        character: { __typename: "Character", id: "1", name: "Doctor Strange" },
+      },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: { id: "1" },
+    });
+  }
+
+  await expect(
+    execute({ variables: { id: "2" } })
+  ).resolves.toEqualApolloQueryResult({
+    data: {
+      character: { __typename: "Character", id: "2", name: "Hulk" },
+    },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: { character: { __typename: "Character", id: "2" } },
+      called: true,
+      loading: true,
+      networkStatus: NetworkStatus.setVariables,
+      previousData: {
+        character: { __typename: "Character", id: "1", name: "Doctor Strange" },
+      },
+      variables: { id: "2" },
+    });
+  }
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: { character: { __typename: "Character", id: "2", name: "Hulk" } },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: {
+        character: { __typename: "Character", id: "2" },
+      },
+      variables: { id: "2" },
+    });
+  }
+
+  await expect(takeSnapshot).not.toRerender();
+});
+
+test("applies updated `fetchPolicy` on next fetch when it changes between renders", async () => {
+  const { query, mocks } = setupVariablesCase();
+
+  const client = new ApolloClient({
+    cache: new InMemoryCache(),
+    link: new MockLink(mocks),
+  });
+
+  client.writeQuery({
+    query,
+    data: {
+      character: { __typename: "Character", id: "1", name: "Spider-Cache" },
+    },
+    variables: { id: "1" },
+  });
+
+  client.writeQuery({
+    query,
+    data: {
+      character: { __typename: "Character", id: "2", name: "Cached Widow" },
+    },
+    variables: { id: "2" },
+  });
+
+  using _disabledAct = disableActEnvironment();
+  const { takeSnapshot, getCurrentSnapshot, rerender } =
+    await renderHookToSnapshotStream(
+      ({ fetchPolicy }) => useLazyQuery(query, { fetchPolicy }),
+      {
+        initialProps: { fetchPolicy: "cache-first" as WatchQueryFetchPolicy },
+        wrapper: ({ children }) => (
+          <ApolloProvider client={client}>{children}</ApolloProvider>
+        ),
+      }
+    );
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: undefined,
+      called: false,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      // @ts-expect-error should be undefined
+      variables: {},
+    });
+  }
+
+  const [execute] = getCurrentSnapshot();
+
+  await expect(
+    execute({ variables: { id: "1" } })
+  ).resolves.toEqualApolloQueryResult({
+    data: {
+      character: { __typename: "Character", id: "1", name: "Spider-Cache" },
+    },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: {
+        character: { __typename: "Character", id: "1", name: "Spider-Cache" },
+      },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: { id: "1" },
+    });
+  }
+
+  await rerender({ fetchPolicy: "cache-and-network" });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: {
+        character: { __typename: "Character", id: "1", name: "Spider-Cache" },
+      },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: { id: "1" },
+    });
+  }
+
+  await expect(
+    execute({ variables: { id: "2" } })
+  ).resolves.toEqualApolloQueryResult({
+    data: {
+      character: { __typename: "Character", id: "2", name: "Black Widow" },
+    },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: {
+        character: { __typename: "Character", id: "2", name: "Cached Widow" },
+      },
+      called: true,
+      loading: true,
+      networkStatus: NetworkStatus.setVariables,
+      previousData: {
+        character: { __typename: "Character", id: "1", name: "Spider-Cache" },
+      },
+      variables: { id: "2" },
+    });
+  }
+
+  {
+    const [, result] = await takeSnapshot();
 
-      // Loading
-      await takeSnapshot();
+    expect(result).toEqualLazyQueryResult({
+      data: {
+        character: { __typename: "Character", id: "2", name: "Black Widow" },
+      },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: {
+        character: { __typename: "Character", id: "2", name: "Cached Widow" },
+      },
+      variables: { id: "2" },
+    });
+  }
 
-      {
-        const [, result] = await takeSnapshot();
+  await expect(takeSnapshot).not.toRerender();
+});
 
-        expect(result).toEqualQueryResult({
-          data: {
-            currentUser: {
-              __typename: "User",
-              id: 1,
-              name: "Test User",
-            },
-          },
-          called: true,
-          loading: false,
-          networkStatus: NetworkStatus.ready,
-          previousData: undefined,
-          variables: {},
-        });
-      }
+test("renders loading states at appropriate times on next fetch after updating `notifyOnNetworkStatusChange`", async () => {
+  const { query } = setupSimpleCase();
 
-      client.writeQuery({
-        query,
-        data: {
-          currentUser: {
-            __typename: "User",
-            id: 1,
-            name: "Test User (updated)",
-            age: 35,
-          },
-        },
-      });
+  const client = new ApolloClient({
+    cache: new InMemoryCache(),
+    link: new MockLink([
+      { request: { query }, result: { data: { greeting: "Hello 1" } } },
+      { request: { query }, result: { data: { greeting: "Hello 2" } } },
+      { request: { query }, result: { data: { greeting: "Hello 3" } } },
+    ]),
+  });
 
+  using _disabledAct = disableActEnvironment();
+  const { takeSnapshot, getCurrentSnapshot, rerender } =
+    await renderHookToSnapshotStream(
+      ({ notifyOnNetworkStatusChange }) =>
+        useLazyQuery(query, {
+          notifyOnNetworkStatusChange,
+          fetchPolicy: "network-only",
+        }),
       {
-        const [, result] = await takeSnapshot();
-
-        expect(result).toEqualQueryResult({
-          data: {
-            currentUser: {
-              __typename: "User",
-              id: 1,
-              name: "Test User (updated)",
-            },
-          },
-          called: true,
-          loading: false,
-          networkStatus: NetworkStatus.ready,
-          previousData: {
-            currentUser: { __typename: "User", id: 1, name: "Test User" },
-          },
-          variables: {},
-        });
+        initialProps: { notifyOnNetworkStatusChange: false },
+        wrapper: ({ children }) => (
+          <ApolloProvider client={client}>{children}</ApolloProvider>
+        ),
       }
+    );
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: undefined,
+      called: false,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: {},
     });
+  }
 
-    it("does not rerender when updating field in named fragment", async () => {
-      type UserFieldsFragment = {
-        __typename: "User";
-        age: number;
-      } & { " $fragmentName"?: "UserFieldsFragment" };
+  const [execute] = getCurrentSnapshot();
 
-      interface Query {
-        currentUser: {
-          __typename: "User";
-          id: number;
-          name: string;
-        } & { " $fragmentRefs"?: { UserFieldsFragment: UserFieldsFragment } };
-      }
+  await expect(execute()).resolves.toEqualApolloQueryResult({
+    data: { greeting: "Hello 1" },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
 
-      const query: TypedDocumentNode<Query, Record<string, never>> = gql`
-        query MaskedQuery {
-          currentUser {
-            id
-            name
-            ...UserFields
-          }
-        }
+  {
+    const [, result] = await takeSnapshot();
 
-        fragment UserFields on User {
-          age
-        }
-      `;
+    expect(result).toEqualLazyQueryResult({
+      data: { greeting: "Hello 1" },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: {},
+    });
+  }
 
-      const mocks = [
-        {
-          request: { query },
-          result: {
-            data: {
-              currentUser: {
-                __typename: "User",
-                id: 1,
-                name: "Test User",
-                age: 30,
-              },
-            },
-          },
-          delay: 20,
-        },
-      ];
+  await rerender({ notifyOnNetworkStatusChange: true });
 
-      const client = new ApolloClient({
-        dataMasking: true,
-        cache: new InMemoryCache(),
-        link: new MockLink(mocks),
-      });
+  {
+    const [, result] = await takeSnapshot();
 
-      using _disabledAct = disableActEnvironment();
-      const { takeSnapshot, getCurrentSnapshot } =
-        await renderHookToSnapshotStream(() => useLazyQuery(query), {
-          wrapper: ({ children }) => (
-            <ApolloProvider client={client}>{children}</ApolloProvider>
-          ),
-        });
+    expect(result).toEqualLazyQueryResult({
+      data: { greeting: "Hello 1" },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: undefined,
+      variables: {},
+    });
+  }
 
-      // initial render
-      await takeSnapshot();
+  await expect(execute()).resolves.toEqualApolloQueryResult({
+    data: { greeting: "Hello 2" },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
+  });
 
-      const [execute] = getCurrentSnapshot();
-      await execute();
+  {
+    const [, result] = await takeSnapshot();
 
-      // Loading
-      await takeSnapshot();
+    expect(result).toEqualLazyQueryResult({
+      data: { greeting: "Hello 1" },
+      called: true,
+      loading: true,
+      networkStatus: NetworkStatus.loading,
+      previousData: undefined,
+      variables: {},
+    });
+  }
 
-      {
-        const [, result] = await takeSnapshot();
+  {
+    const [, result] = await takeSnapshot();
 
-        expect(result).toEqualQueryResult({
-          data: {
-            currentUser: {
-              __typename: "User",
-              id: 1,
-              name: "Test User",
-            },
-          },
-          called: true,
-          loading: false,
-          networkStatus: NetworkStatus.ready,
-          previousData: undefined,
-          variables: {},
-        });
-      }
+    expect(result).toEqualLazyQueryResult({
+      data: { greeting: "Hello 2" },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: { greeting: "Hello 1" },
+      variables: {},
+    });
+  }
 
-      client.writeQuery({
-        query,
-        data: {
-          currentUser: {
-            __typename: "User",
-            id: 1,
-            name: "Test User",
-            age: 35,
-          },
-        },
-      });
+  await rerender({ notifyOnNetworkStatusChange: false });
 
-      await expect(takeSnapshot).not.toRerender();
+  {
+    const [, result] = await takeSnapshot();
 
-      expect(client.readQuery({ query })).toEqual({
-        currentUser: {
-          __typename: "User",
-          id: 1,
-          name: "Test User",
-          age: 35,
-        },
-      });
+    expect(result).toEqualLazyQueryResult({
+      data: { greeting: "Hello 2" },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: { greeting: "Hello 1" },
+      variables: {},
     });
+  }
+
+  await expect(execute()).resolves.toEqualApolloQueryResult({
+    data: { greeting: "Hello 3" },
+    loading: false,
+    networkStatus: NetworkStatus.ready,
+    partial: false,
   });
+
+  {
+    const [, result] = await takeSnapshot();
+
+    expect(result).toEqualLazyQueryResult({
+      data: { greeting: "Hello 3" },
+      called: true,
+      loading: false,
+      networkStatus: NetworkStatus.ready,
+      previousData: { greeting: "Hello 2" },
+      variables: {},
+    });
+  }
+
+  await expect(takeSnapshot).not.toRerender();
 });
 
 describe.skip("Type Tests", () => {
   test("NoInfer prevents adding arbitrary additional variables", () => {
     const typedNode = {} as TypedDocumentNode<{ foo: string }, { bar: number }>;
-    const [_, { variables }] = useLazyQuery(typedNode, {
+    const [execute, { variables }] = useLazyQuery(typedNode);
+
+    void execute({
       variables: {
         bar: 4,
         // @ts-expect-error
         nonExistingVariable: "string",
       },
     });
+
     variables?.bar;
     // @ts-expect-error
     variables?.nonExistingVariable;
@@ -3511,4 +5514,145 @@ describe.skip("Type Tests", () => {
       expectTypeOf(data).toEqualTypeOf<Query | undefined>();
     }
   });
+
+  test("variables are optional and can be anything with an DocumentNode", () => {
+    const query = gql``;
+
+    const [execute] = useLazyQuery(query);
+
+    void execute();
+    void execute({});
+    void execute({ variables: {} });
+    void execute({ variables: { foo: "bar" } });
+    void execute({ variables: { bar: "baz" } });
+  });
+
+  test("variables are optional and can be anything with unspecified TVariables on a TypedDocumentNode", () => {
+    const query: TypedDocumentNode<{ greeting: string }> = gql``;
+
+    const [execute] = useLazyQuery(query);
+
+    void execute();
+    void execute({});
+    void execute({ variables: {} });
+    void execute({ variables: { foo: "bar" } });
+    void execute({ variables: { bar: "baz" } });
+  });
+
+  test("variables are optional when TVariables are empty", () => {
+    const query: TypedDocumentNode<
+      { greeting: string },
+      Record<string, never>
+    > = gql``;
+
+    const [execute] = useLazyQuery(query);
+
+    void execute();
+    void execute({});
+    void execute({ variables: {} });
+    // @ts-expect-error unknown variables
+    void execute({ variables: { foo: "bar" } });
+  });
+
+  test("does not allow variables when TVariables is `never`", () => {
+    const query: TypedDocumentNode<{ greeting: string }, never> = gql``;
+
+    const [execute] = useLazyQuery(query);
+
+    void execute();
+    void execute({});
+    void execute({ variables: {} });
+    // @ts-expect-error unknown variables
+    void execute({ variables: { foo: "bar" } });
+  });
+
+  test("optional variables are optional", () => {
+    const query: TypedDocumentNode<{ posts: string[] }, { limit?: number }> =
+      gql``;
+
+    const [execute] = useLazyQuery(query);
+
+    void execute();
+    void execute({});
+    void execute({ variables: {} });
+    void execute({ variables: { limit: 10 } });
+    void execute({
+      variables: {
+        // @ts-expect-error unknown variables
+        foo: "bar",
+      },
+    });
+    void execute({
+      variables: {
+        limit: 10,
+        // @ts-expect-error unknown variables
+        foo: "bar",
+      },
+    });
+  });
+
+  test("enforces required variables when TVariables includes required variables", () => {
+    const query: TypedDocumentNode<{ character: string }, { id: string }> =
+      gql``;
+
+    const [execute] = useLazyQuery(query);
+
+    // @ts-expect-error empty variables
+    void execute();
+    // @ts-expect-error empty variables
+    void execute({});
+    // @ts-expect-error empty variables
+    void execute({ variables: {} });
+    void execute({ variables: { id: "1" } });
+    void execute({
+      variables: {
+        // @ts-expect-error unknown variables
+        foo: "bar",
+      },
+    });
+    void execute({
+      variables: {
+        id: "1",
+        // @ts-expect-error unknown variables
+        foo: "bar",
+      },
+    });
+  });
+
+  test("requires variables with mixed TVariables", () => {
+    const query: TypedDocumentNode<
+      { character: string },
+      { id: string; language?: string }
+    > = gql``;
+
+    const [execute] = useLazyQuery(query);
+
+    // @ts-expect-error empty variables
+    void execute();
+    // @ts-expect-error empty variables
+    void execute({});
+    // @ts-expect-error empty variables
+    void execute({ variables: {} });
+    void execute({ variables: { id: "1" } });
+    void execute({
+      // @ts-expect-error missing required variables
+      variables: { language: "en" },
+    });
+    void execute({ variables: { id: "1", language: "en" } });
+    void execute({
+      variables: {
+        id: "1",
+        // @ts-expect-error unknown variables
+        foo: "bar",
+      },
+    });
+    void execute({
+      variables: {
+        id: "1",
+        language: "en",
+        // @ts-expect-error unknown variables
+        foo: "bar",
+      },
+    });
+  });
 });
diff --git a/src/react/hooks/__tests__/useQuery.test.tsx b/src/react/hooks/__tests__/useQuery.test.tsx
--- a/src/react/hooks/__tests__/useQuery.test.tsx
+++ b/src/react/hooks/__tests__/useQuery.test.tsx
@@ -43,7 +43,7 @@ import {
   spyOnConsole,
 } from "../../../testing/internal/index.js";
 import { QueryResult } from "../../types/types.js";
-import { useLazyQuery } from "../useLazyQuery.js";
+import { LazyQueryResult, useLazyQuery } from "../useLazyQuery.js";
 import { useMutation } from "../useMutation.js";
 import { useQuery } from "../useQuery.js";
 
@@ -5400,7 +5400,7 @@ describe("useQuery Hook", () => {
     const renderStream = createRenderStream({
       initialSnapshot: {
         useQueryResult: null as QueryResult<Query1, Variables> | null,
-        useLazyQueryResult: null as QueryResult<Query2, Variables> | null,
+        useLazyQueryResult: null as LazyQueryResult<Query2, Variables> | null,
       },
     });
 
@@ -5434,14 +5434,16 @@ describe("useQuery Hook", () => {
       });
 
       const [execute, useLazyQueryResult] = useLazyQuery(query2, {
-        variables: { id: 1 },
+        notifyOnNetworkStatusChange: true,
       });
 
       renderStream.replaceSnapshot({ useQueryResult, useLazyQueryResult });
 
       return (
         <>
-          <button onClick={() => execute()}>Run 2nd query</button>
+          <button onClick={() => execute({ variables: { id: 1 } })}>
+            Run 2nd query
+          </button>
           <button
             onClick={() => {
               // Intentionally use reobserve here as opposed to refetch to
@@ -5474,14 +5476,14 @@ describe("useQuery Hook", () => {
         variables: { id: 1 },
       });
 
-      expect(snapshot.useLazyQueryResult!).toEqualQueryResult({
+      expect(snapshot.useLazyQueryResult!).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
         previousData: undefined,
-        variables: { id: 1 },
+        // @ts-expect-error should be undefined
+        variables: {},
       });
     }
 
@@ -5500,14 +5502,14 @@ describe("useQuery Hook", () => {
         variables: { id: 1 },
       });
 
-      expect(snapshot.useLazyQueryResult!).toEqualQueryResult({
+      expect(snapshot.useLazyQueryResult!).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
         previousData: undefined,
-        variables: { id: 1 },
+        // @ts-expect-error should be undefined
+        variables: {},
       });
     }
 
@@ -5528,7 +5530,7 @@ describe("useQuery Hook", () => {
         variables: { id: 1 },
       });
 
-      expect(snapshot.useLazyQueryResult!).toEqualQueryResult({
+      expect(snapshot.useLazyQueryResult!).toEqualLazyQueryResult({
         data: undefined,
         called: true,
         loading: true,
@@ -5567,7 +5569,7 @@ describe("useQuery Hook", () => {
         partial: true,
       });
 
-      expect(snapshot.useLazyQueryResult!).toEqualQueryResult({
+      expect(snapshot.useLazyQueryResult!).toEqualLazyQueryResult({
         data: { person: { __typename: "Person", id: 1, lastName: "Doe" } },
         called: true,
         loading: false,
@@ -5591,7 +5593,7 @@ describe("useQuery Hook", () => {
         variables: { id: 1 },
       });
 
-      expect(snapshot.useLazyQueryResult!).toEqualQueryResult({
+      expect(snapshot.useLazyQueryResult!).toEqualLazyQueryResult({
         data: { person: { __typename: "Person", id: 1, lastName: "Doe" } },
         called: true,
         loading: false,
@@ -5630,7 +5632,7 @@ describe("useQuery Hook", () => {
         partial: true,
       });
 
-      expect(snapshot.useLazyQueryResult!).toEqualQueryResult({
+      expect(snapshot.useLazyQueryResult!).toEqualLazyQueryResult({
         data: { person: { __typename: "Person", id: 1, lastName: "Doe" } },
         called: true,
         loading: false,
@@ -5690,7 +5692,7 @@ describe("useQuery Hook", () => {
     const renderStream = createRenderStream({
       initialSnapshot: {
         useQueryResult: null as QueryResult<Query1, Variables> | null,
-        useLazyQueryResult: null as QueryResult<Query2, Variables> | null,
+        useLazyQueryResult: null as LazyQueryResult<Query2, Variables> | null,
       },
     });
 
@@ -5730,12 +5732,16 @@ describe("useQuery Hook", () => {
       });
 
       const [execute, useLazyQueryResult] = useLazyQuery(query2, {
-        variables: { id: 1 },
+        notifyOnNetworkStatusChange: true,
       });
 
       renderStream.replaceSnapshot({ useQueryResult, useLazyQueryResult });
 
-      return <button onClick={() => execute()}>Run 2nd query</button>;
+      return (
+        <button onClick={() => execute({ variables: { id: 1 } })}>
+          Run 2nd query
+        </button>
+      );
     }
 
     await renderStream.render(<App />, {
@@ -5756,14 +5762,14 @@ describe("useQuery Hook", () => {
         variables: { id: 1 },
       });
 
-      expect(snapshot.useLazyQueryResult!).toEqualQueryResult({
+      expect(snapshot.useLazyQueryResult!).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
         previousData: undefined,
-        variables: { id: 1 },
+        // @ts-expect-error should be undefined
+        variables: {},
       });
     }
 
@@ -5782,14 +5788,14 @@ describe("useQuery Hook", () => {
         variables: { id: 1 },
       });
 
-      expect(snapshot.useLazyQueryResult!).toEqualQueryResult({
+      expect(snapshot.useLazyQueryResult!).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
         previousData: undefined,
-        variables: { id: 1 },
+        // @ts-expect-error should be undefined
+        variables: {},
       });
     }
 
@@ -5810,7 +5816,7 @@ describe("useQuery Hook", () => {
         variables: { id: 1 },
       });
 
-      expect(snapshot.useLazyQueryResult!).toEqualQueryResult({
+      expect(snapshot.useLazyQueryResult!).toEqualLazyQueryResult({
         data: undefined,
         called: true,
         loading: true,
@@ -5838,7 +5844,7 @@ describe("useQuery Hook", () => {
         variables: { id: 1 },
       });
 
-      expect(snapshot.useLazyQueryResult!).toEqualQueryResult({
+      expect(snapshot.useLazyQueryResult!).toEqualLazyQueryResult({
         data: {
           person: {
             __typename: "Person",
@@ -5873,7 +5879,7 @@ describe("useQuery Hook", () => {
         variables: { id: 1 },
       });
 
-      expect(snapshot.useLazyQueryResult!).toEqualQueryResult({
+      expect(snapshot.useLazyQueryResult!).toEqualLazyQueryResult({
         data: {
           person: {
             __typename: "Person",
@@ -5940,7 +5946,7 @@ describe("useQuery Hook", () => {
     const renderStream = createRenderStream({
       initialSnapshot: {
         useQueryResult: null as QueryResult<Query1, Variables> | null,
-        useLazyQueryResult: null as QueryResult<Query2, Variables> | null,
+        useLazyQueryResult: null as LazyQueryResult<Query2, Variables> | null,
       },
     });
 
@@ -5980,12 +5986,16 @@ describe("useQuery Hook", () => {
       });
 
       const [execute, useLazyQueryResult] = useLazyQuery(query2, {
-        variables: { id: 1 },
+        notifyOnNetworkStatusChange: true,
       });
 
       renderStream.replaceSnapshot({ useQueryResult, useLazyQueryResult });
 
-      return <button onClick={() => execute()}>Run 2nd query</button>;
+      return (
+        <button onClick={() => execute({ variables: { id: 1 } })}>
+          Run 2nd query
+        </button>
+      );
     }
 
     await renderStream.render(<App />, {
@@ -6006,14 +6016,14 @@ describe("useQuery Hook", () => {
         variables: { id: 1 },
       });
 
-      expect(snapshot.useLazyQueryResult!).toEqualQueryResult({
+      expect(snapshot.useLazyQueryResult!).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
         previousData: undefined,
-        variables: { id: 1 },
+        // @ts-expect-error should be undefined
+        variables: {},
       });
     }
 
@@ -6032,14 +6042,14 @@ describe("useQuery Hook", () => {
         variables: { id: 1 },
       });
 
-      expect(snapshot.useLazyQueryResult!).toEqualQueryResult({
+      expect(snapshot.useLazyQueryResult!).toEqualLazyQueryResult({
         data: undefined,
-        error: undefined,
         called: false,
         loading: false,
         networkStatus: NetworkStatus.ready,
         previousData: undefined,
-        variables: { id: 1 },
+        // @ts-expect-error should be undefined
+        variables: {},
       });
     }
 
@@ -6060,7 +6070,7 @@ describe("useQuery Hook", () => {
         variables: { id: 1 },
       });
 
-      expect(snapshot.useLazyQueryResult!).toEqualQueryResult({
+      expect(snapshot.useLazyQueryResult!).toEqualLazyQueryResult({
         data: undefined,
         called: true,
         loading: true,
@@ -6085,7 +6095,7 @@ describe("useQuery Hook", () => {
         variables: { id: 1 },
       });
 
-      expect(snapshot.useLazyQueryResult!).toEqualQueryResult({
+      expect(snapshot.useLazyQueryResult!).toEqualLazyQueryResult({
         data: {
           person: {
             __typename: "Person",
diff --git a/src/react/ssr/__tests__/useLazyQuery.test.tsx b/src/react/ssr/__tests__/useLazyQuery.test.tsx
deleted file mode 100644
--- a/src/react/ssr/__tests__/useLazyQuery.test.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-/** @jest-environment node */
-import { DocumentNode } from "graphql";
-import { gql } from "graphql-tag";
-import React from "react";
-
-import { InMemoryCache } from "@apollo/client/cache";
-import { ApolloClient } from "@apollo/client/core";
-import { ApolloProvider } from "@apollo/client/react/context";
-import { useLazyQuery } from "@apollo/client/react/hooks";
-import { renderToStringWithData } from "@apollo/client/react/ssr";
-import { mockSingleLink } from "@apollo/client/testing";
-
-describe("useLazyQuery Hook SSR", () => {
-  const CAR_QUERY: DocumentNode = gql`
-    query {
-      cars {
-        make
-        model
-        vin
-      }
-    }
-  `;
-
-  const CAR_RESULT_DATA = {
-    cars: [
-      {
-        make: "Audi",
-        model: "RS8",
-        vin: "DOLLADOLLABILL",
-        __typename: "Car",
-      },
-    ],
-  };
-
-  it("should run query only after calling the lazy mode execute function", () => {
-    const link = mockSingleLink({
-      request: { query: CAR_QUERY },
-      result: { data: CAR_RESULT_DATA },
-    });
-
-    const client = new ApolloClient({
-      cache: new InMemoryCache(),
-      link,
-      ssrMode: true,
-    });
-
-    const Component = () => {
-      let html = null;
-      const [execute, { loading, called, data }] = useLazyQuery(CAR_QUERY);
-
-      if (!loading && !called) {
-        execute();
-      }
-
-      if (!loading && called) {
-        expect(loading).toEqual(false);
-        expect(data).toEqual(CAR_RESULT_DATA);
-        html = <p>{data.cars[0].make}</p>;
-      }
-
-      return html;
-    };
-
-    const app = (
-      <ApolloProvider client={client}>
-        <Component />
-      </ApolloProvider>
-    );
-
-    return renderToStringWithData(app).then((markup) => {
-      expect(markup).toMatch(/Audi/);
-    });
-  });
-});
diff --git a/src/testing/matchers/index.d.ts b/src/testing/matchers/index.d.ts
--- a/src/testing/matchers/index.d.ts
+++ b/src/testing/matchers/index.d.ts
@@ -5,11 +5,16 @@ import type {
   FetchResult,
   OperationVariables,
 } from "../../core/index.js";
-import type { QueryRef, QueryResult } from "../../react/index.js";
+import type {
+  LazyQueryResult,
+  QueryRef,
+  QueryResult,
+} from "../../react/index.js";
 import { NextRenderOptions, ObservableStream } from "../internal/index.js";
 import { RenderStreamMatchers } from "@testing-library/react-render-stream/expect";
 import { TakeOptions } from "../internal/ObservableStream.js";
 import { CheckedKeys } from "./toEqualQueryResult.js";
+import { CheckedLazyQueryResult } from "./toEqualLazyQueryResult.js";
 
 interface ApolloCustomMatchers<R = void, T = {}> {
   /**
@@ -89,6 +94,14 @@ interface ApolloCustomMatchers<R = void, T = {}> {
     (expected: ApolloQueryResult<TData>) => R
   : { error: "matchers needs to be called on an ApolloQueryResult" };
 
+  toEqualLazyQueryResult: T extends (
+    LazyQueryResult<infer TData, infer TVariables>
+  ) ?
+    (expected: CheckedLazyQueryResult<TData, TVariables>) => R
+  : T extends Promise<LazyQueryResult<infer TData, infer TVariables>> ?
+    (expected: CheckedLazyQueryResult<TData, TVariables>) => R
+  : { error: "matchers needs to be called on a LazyQueryResult" };
+
   toEqualQueryResult: T extends QueryResult<infer TData, infer TVariables> ?
     (expected: Pick<QueryResult<TData, TVariables>, CheckedKeys>) => R
   : T extends Promise<QueryResult<infer TData, infer TVariables>> ?
diff --git a/src/testing/matchers/index.ts b/src/testing/matchers/index.ts
--- a/src/testing/matchers/index.ts
+++ b/src/testing/matchers/index.ts
@@ -13,6 +13,7 @@ import { toEmitValue } from "./toEmitValue.js";
 import { toEmitValueStrict } from "./toEmitValueStrict.js";
 import { toEqualApolloQueryResult } from "./toEqualApolloQueryResult.js";
 import { toEqualFetchResult } from "./toEqualFetchResult.js";
+import { toEqualLazyQueryResult } from "./toEqualLazyQueryResult.js";
 import { toEqualQueryResult } from "./toEqualQueryResult.js";
 import { toHaveSuspenseCacheEntryUsing } from "./toHaveSuspenseCacheEntryUsing.js";
 import { toMatchDocument } from "./toMatchDocument.js";
@@ -29,6 +30,7 @@ expect.extend({
   toEmitValueStrict,
   toEqualApolloQueryResult,
   toEqualFetchResult,
+  toEqualLazyQueryResult,
   toEqualQueryResult,
   toBeDisposed,
   toHaveSuspenseCacheEntryUsing,
diff --git a/src/testing/matchers/toEqualLazyQueryResult.ts b/src/testing/matchers/toEqualLazyQueryResult.ts
new file mode 100644
--- /dev/null
+++ b/src/testing/matchers/toEqualLazyQueryResult.ts
@@ -0,0 +1,75 @@
+import { iterableEquality } from "@jest/expect-utils";
+import type { MatcherFunction } from "expect";
+
+import type { OperationVariables } from "@apollo/client/core";
+import type { LazyQueryResult } from "@apollo/client/react";
+
+const CHECKED_KEYS = [
+  "loading",
+  "error",
+  "data",
+  "variables",
+  "networkStatus",
+  "called",
+  "previousData",
+] as const;
+
+export type CheckedLazyQueryResult<
+  TData,
+  TVariables extends OperationVariables,
+> = Pick<LazyQueryResult<TData, TVariables>, (typeof CHECKED_KEYS)[number]>;
+
+const hasOwnProperty = (obj: Record<string, any>, key: string) =>
+  Object.prototype.hasOwnProperty.call(obj, key);
+
+export const toEqualLazyQueryResult: MatcherFunction<
+  [lazyQueryResult: CheckedLazyQueryResult<any, any>]
+> = function (actual, expected) {
+  const lazyQueryResult = actual as LazyQueryResult<any, any>;
+  const hint = this.utils.matcherHint(
+    this.isNot ? ".not.toEqualLazyQueryResult" : "toEqualLazyQueryResult",
+    "lazyQueryResult",
+    "expected",
+    { isNot: this.isNot, promise: this.promise }
+  );
+
+  const checkedLazyQueryResult = CHECKED_KEYS.reduce(
+    (memo, key) => {
+      if (hasOwnProperty(lazyQueryResult, key)) {
+        memo[key] = lazyQueryResult[key];
+      }
+
+      return memo;
+    },
+    {} as Partial<LazyQueryResult<any, any>>
+  );
+
+  const pass = this.equals(
+    checkedLazyQueryResult,
+    expected,
+    // https://github.com/jestjs/jest/blob/22029ba06b69716699254bb9397f2b3bc7b3cf3b/packages/expect/src/matchers.ts#L62-L67
+    [...this.customTesters, iterableEquality],
+    true
+  );
+
+  return {
+    pass,
+    message: () => {
+      if (pass) {
+        return hint + `\n\nExpected: not ${this.utils.printExpected(expected)}`;
+      }
+
+      return (
+        hint +
+        "\n\n" +
+        this.utils.printDiffOrStringify(
+          expected,
+          checkedLazyQueryResult,
+          "Expected",
+          "Received",
+          true
+        )
+      );
+    },
+  };
+};