Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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/

like image 307
Evanss Avatar asked Nov 06 '22 02:11

Evanss


1 Answers

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"
          }
        ]
      }
    })
  }
];
like image 145
mcernak Avatar answered Nov 11 '22 15:11

mcernak