Apollo's MockedProvider doenst work when a component has multiple useQuery's?
Asked Answered
G

1

6

I have a simple Apollo set up. In this CodeSandbox example I'm using the MockedProvider but the live one also works:

https://codesandbox.io/s/winter-glitter-o8ug5?file=/src/App.js:0-1164

import { ApolloClient, InMemoryCache } from "@apollo/client";
import { gql, useQuery, ApolloProvider } from "@apollo/client";
import { MockedProvider } from "@apollo/client/testing";

const client = new ApolloClient({
  uri: "https://48p1r2roz4.sse.codesandbox.io",
  cache: new InMemoryCache()
});

const EXCHANGE_RATES_1 = gql`
  query GetExchangeRates {
    rates(currency: "USD") {
      rate
    }
  }
`;

const mocks = [
  {
    request: {
      query: EXCHANGE_RATES_1
    },
    result: {
      data: {
        rates: [
          {
            __typename: "ExchangeRate",
            rate: "123"
          }
        ]
      }
    }
  }
];

const MyComponent = () => {
  const { data: data1, loading: loading1 } = useQuery(EXCHANGE_RATES_1);
  if (loading1) return <p>Loading...</p>;
  return (
    <div>
      <h1>{data1?.rates[0].rate}</h1>
    </div>
  );
};

const AppMocked = () => {
  return (
    <MockedProvider addTypename={false} mocks={mocks}>
      <MyComponent />
    </MockedProvider>
  );
};

const AppLive = () => {
  return (
    <ApolloProvider client={client}>
      <MyComponent />
    </ApolloProvider>
  );
};

export default AppMocked;

I need to have an additional useQuery in MyComponent. I know this is a weird example as it should just be one query but it illustrates the issue I'm running into.

https://codesandbox.io/s/magical-dewdney-j451d?file=/src/App.js:0-1630

import { ApolloClient, InMemoryCache } from "@apollo/client";
import { gql, useQuery, ApolloProvider } from "@apollo/client";
import { MockedProvider } from "@apollo/client/testing";

const client = new ApolloClient({
  uri: "https://48p1r2roz4.sse.codesandbox.io",
  cache: new InMemoryCache()
});

const EXCHANGE_RATES_1 = gql`
  query GetExchangeRates {
    rates(currency: "USD") {
      rate
    }
  }
`;
const EXCHANGE_RATES_2 = gql`
  query GetExchangeRates {
    rates(currency: "USD") {
      currency
    }
  }
`;

const mocks = [
  {
    request: {
      query: EXCHANGE_RATES_1
    },
    result: {
      data: {
        rates: [
          {
            __typename: "ExchangeRate",
            rate: "123"
          }
        ]
      }
    }
  },
  {
    request: {
      query: EXCHANGE_RATES_2
    },
    result: {
      data: {
        rates: [
          {
            __typename: "ExchangeRate",
            currency: "YOLO"
          }
        ]
      }
    }
  }
];

const MyComponent = () => {
  const { data: data1, loading: loading1 } = useQuery(EXCHANGE_RATES_1);
  const { data: data2, loading: loading2 } = useQuery(EXCHANGE_RATES_2);
  if (loading1 || loading2) return <p>Loading...</p>;
  return (
    <div>
      <h1>{data1?.rates[0].rate}</h1>
      <h2>{data2?.rates[0].currency}</h2>
    </div>
  );
};

const AppMocked = () => {
  return (
    <MockedProvider addTypename={false} mocks={mocks}>
      <MyComponent />
    </MockedProvider>
  );
};

const AppLive = () => {
  return (
    <ApolloProvider client={client}>
      <MyComponent />
    </ApolloProvider>
  );
};

export default AppMocked;

The live version works fine but the mocked version has an empty H1. When I log out data1 I can see that it initially does have data but on subsequent renders becomes 'undefined'

I couldn't see anything in the docs to explain why the mocking isnt working: https://www.apollographql.com/docs/react/development-testing/testing/

Gentlefolk answered 3/3, 2021 at 10:38 Comment(0)
S
10

For some reason, in your setup, the queries are called more than once.

This causes a "No more mocked responses for the query" error behind the scenes (see mockLink.ts) as each query call consumes one mocked response.
The first time the query is called, it returns the mocked data, which is why you initially see it. The next call though throws an error and sets the data to undefined.

I found two ways to solve this:

Option 1 Disable caching on the MockedProvider by setting the fetch-policy to no-cache:

<MockedProvider 
  mocks={mocks} 
  defaultOptions={{
    watchQuery: { fetchPolicy: 'no-cache' },
    query: { fetchPolicy: 'no-cache' },
  }}
>
  <MyComponent />
</MockedProvider>

Option 2 Allow the mocked response to be used more than once by providing a newData function:

const mocks = [
  {
    request: {
      query: EXCHANGE_RATES_1
    },
    result: {
      data: {
        rates: [
          {
            __typename: "ExchangeRate",
            rate: "123"
          }
        ]
      }
    },
    newData: () => ({
      data: {
        rates: [
          {
            __typename: "ExchangeRate",
            rate: "123"
          }
        ]
      }
    })
  },
  {
    request: {
      query: EXCHANGE_RATES_2
    },
    result: {
      data: {
        rates: [
          {
            __typename: "ExchangeRate",
            currency: "YOLO"
          }
        ]
      }
    },
    newData: () => ({
      data: {
        rates: [
          {
            __typename: "ExchangeRate",
            currency: "YOLO"
          }
        ]
      }
    })
  }
];
Slog answered 13/3, 2021 at 12:11 Comment(6)
Interesting, is newData documented anywhere? I cant see it on this page: apollographql.com/docs/react/development-testing/testingGentlefolk
I haven't seen it properly documented anywhere. I just saw it while going through the source file mockLinks.ts.Slog
I just spend 4 hrs on this problem, thanks a lot for the solution!Extrajudicial
this is where I found github.com/apollographql/apollo-client/blob/main/src/testing/…Maccarthy
Thank you. Option 1 didn't work for me, only option 2Yarrow
Thanks a lot, it saves me days. The first option works for me!Teammate

© 2022 - 2024 — McMap. All rights reserved.