Home > Back-end >  Apollo MockedProvider: “Failed to match x mocks for this query” where x is off by 1. Never sees the
Apollo MockedProvider: “Failed to match x mocks for this query” where x is off by 1. Never sees the

Time:08-08

I’m experiencing the most bizarre Apollo behavior… in my tests, any time I issue a query for which I have a mock, Apollo magically can’t find it.

I have 5 mocks covering queries based on a collectionId and some pagination variables. When I have the component under test look up ID 834940, I get this:

Expected variables: {"page":1,"perPage":20,"collectionId":834940}

Failed to match 4 mocks for this query, which had the following variables:
  {"collectionId":374276,"page":1,"perPage":20}
  {"collectionId":112805,"page":1,"perPage":20}
  {"collectionId":238350,"page":1,"perPage":20}
  {"collectionId":-1,"page":1,"perPage":20}

Note that it says “4 mocks”. So it’s not seeing the mock I need, and the query fails. Make sense… until I change the component under test to look up ID 112805, which you can clearly see listed in the 4 mocks Apollo ostensibly knows about.

Expected variables: {"page":1,"perPage":20,"collectionId":112805}

Failed to match 4 mocks for this query, which had the following variables:
  {"collectionId":374276,"page":1,"perPage":20}
  {"collectionId":834940,"page":1,"perPage":20}
  {"collectionId":238350,"page":1,"perPage":20}
  {"collectionId":-1,"page":1,"perPage":20}

It can still only see 4 mocks, but the mocks it can see have changed. Now, all of a sudden, 112805 is missing in the mocks it sees. And now it CAN see the mock for 834940, the ID I queried for last time! So basically, whatever I ask for is the query it can’t resolve!!

Has anyone encountered this before?


Context

Collection.tsx:

A functional component with 3 different useQuery calls. Only the third one is failing:

import collectionQuery from './Collection.gql';

export type CollectionProps = {
  className?: string;
  collection: {
    id: number;
    name: string;
  };
};

export function Collection( props: CollectionProps ) {
  /* [useQuery #1] */

  /* [useQuery #2] */

  // useQuery #3
  const {
    data, error, loading, refetch,
  } = useQuery<CollectionQuery, CollectionQueryVariables>( collectionQuery, {
    skip: variables.collectionId === -1,
    variables,
  } );

  return null;
}

queryMocks.ts:

import collectionQuery from './Collection.gql';
import { CollectionQuery_collection } from '../../../graphqlTypes/CollectionQuery';

const page = 1;
const perPage = 20;

export const firstCollection: CollectionQuery_collection = {
  id: 374276,
  name: 'First Collection',
};

export const secondCollection: CollectionQuery_collection = {
  id: 834940,
  name: 'Second Collection',
};

export const thirdCollection: CollectionQuery_collection = {
  id: 112805,
  name: 'Third Collection',
}

export const fourthCollection: CollectionQuery_collection = {
  id: 238350,
  name: 'Fourth Collection',
};

export const fifthCollection: CollectionQuery_collection = {
  id: -1,
  name 'Fifth Collection (Error)',
};

export const queryMocks: MockedResponse[] = [
  {
    request: {
      query: collectionQuery,
      variables: {
        collectionId: firstCollection.id,
        page,
        perPage,
      },
    },
    result: {
      data: {
        collection: firstCollection,
      },
    },
  },
  {
    request: {
      query: collectionQuery,
      variables: {
        collectionId: secondCollection.id,
        page,
        perPage,
      },
    },
    result: {
      data: {
        collection: secondCollection,
      },
    },
  },
  {
    request: {
      query: collectionQuery,
      variables: {
        collectionId: thirdCollection.id,
        page,
        perPage,
      },
    },
    result: {
      data: {
        collection: thirdCollection,
      },
    },
  }, {
    request: {
      query: collectionQuery,
      variables: {
        collectionId: fourthCollection.id,
        page,
        perPage,
      },
    },
    result: {
      data: {
        collection: fourthCollection,
      },
    },
  }, {
    request: {
      query: collectionQuery,
      variables: {
        collectionId: fifthCollection.id,
        page,
        perPage,
      },
    },
    result: {
      data: {
        collection: fifthCollection,
      },
    },
  },
];

Collection.test.tsx:

import React from 'react';
import { MockedProvider } from '@apollo/client/testing';
import { MemoryRouter } from 'react-router';

import { secondCollection, queryMocks } from './queryMocks';
import { Collection } from './Collection';

it( 'Renders', () => {
  render(
    <MockedProvider mocks={ queryMocks } addTypename={ false }>
      <MemoryRouter>
        <Collection collection={ { id: secondCollection.id } } />
      </MemoryRouter>
    </MockedProvider>,
  );
} );

CodePudding user response:

The component is fetching collectionQuery twice, likely due to a state change. This could be from a useState set* call, or it could be from having multiple useQuerys. In the latter case, when one of the 3 queries resolves (with data changing from undefined to defined), it triggers a component re-render, which then calls the other query or queries again.

The reason why this breaks MockedProvider is because each mock can only be used to resolve a single query. So on the first matching API call, the mock is “spent”, reducing the queryMocks length from 5 to 4. Then, when the component re-renders and calls the same query again, Apollo can no longer find a matching mock. So to solve this, you have to either A.) refactor the component to only call the query once, or B.) add two of the same mock to the queryMocks array, like so:

queryMocks.ts:

const secondMock = {
  request: {
    query: collectionQuery,
    variables: {
      collectionId: secondCollection.id,
      page,
      perPage,
    },
  },
  result: {
    data: {
      collection: secondCollection,
    },
  },
};

export queryMocks: MockedResponse[] = [
  /* [mockOne] */
  mockTwo,
  mockTwo,
  /* [mockThree, ..., mockFive] */
];
  • Related