Apollo-client: Breaking change from 3.0.2 to 3.1.0 [ MockedProvider ]

Created on 8 Aug 2020  路  17Comments  路  Source: apollographql/apollo-client

Intended outcome:
Be able to mock a query using MockProvider

import { MockedProvider } from '@apollo/client/testing';
import { GET_TEACHERS_AND_ADMINS_OF_MY_DISTRICT_QUERY } from '../queries';

const mocks = [
  {
    request: {
      query: GET_TEACHERS_AND_ADMINS_OF_MY_DISTRICT_QUERY,
    },
    result: {
      loading: false,
      error: null,
      data: {
        teachersAndAdminsOfMyDistrict: [
          {
          [...]
...
      <MockedProvider mocks={mocks} addTypename={false}>
        <ComponentThatUsesTheQuery  />
      </MockedProvider>

Actual outcome:

After updating to 3.1.0 tests broke with:

    No more mocked responses for the query: {
      teachersAndAdminsOfMyDistrict {
      [...]

How to reproduce the issue:
Described in the first section but if more info is needed I can add here.

Versions

  System:
    OS: Linux 5.4 Ubuntu 20.04.1 LTS (Focal Fossa)
  Binaries:
    Node: 10.19.0 - /usr/bin/node
    npm: 6.14.4 - /usr/bin/npm
  Browsers:
    Chrome: 84.0.4147.105
    Firefox: 79.0
  npmPackages:
    @apollo/client: ^3.1.0 => 3.1.0 
    apollo-upload-client: ^14.1.0 => 14.1.0 

Most helpful comment

I've resolved this with my post on this one

defaultOptions={{
                                watchQuery: { fetchPolicy: 'no-cache' },
                                query: { fetchPolicy: 'no-cache' },
                            }}

All 17 comments

+1

We're also experiencing this issue

@tyagow as a work around, you can mock useQuery like so:

import * as Apollo from '@apollo/client';

jest.spyOn(Apollo, 'useQuery').mockImplementation(() => {
    return {
        loading: false,
        error: undefined,
        data: { MOCK_DATA },
        refetch: jest.fn()
    }
});

@tyagow as a work around, you can mock useQuery like so:

import * as Apollo from '@apollo/client';

jest.spyOn(Apollo, 'useQuery').mockImplementation(() => {
    return {
        loading: false,
        error: undefined,
        data: { MOCK_DATA },
        refetch: jest.fn()
    }
});

Thank you, great idea!
In the end, I used the new mock wrapper we created that uses the schema.graphql to auto-generate responses, here is a draft:

const mergeResolvers = (target, input) => {
  const inputTypenames = Object.keys(input);
  const merged = inputTypenames.reduce(
    (accum, key) => {
      const inputResolver = input[key];
      if (target[key]) {
        const targetResolver = target[key];
        const resolvedInput = inputResolver();
        const resolvedTarget = targetResolver();
        if (
          !!resolvedTarget &&
          !!resolvedInput &&
          isDict(resolvedTarget) &&
          isDict(resolvedInput)
        ) {
          const newValue = { ...resolvedTarget, ...resolvedInput };
          return {
            ...accum,
            [key]: () => newValue,
          };
        }
      }
      return { ...accum, [key]: inputResolver };
    },
    { ...target }
  );
  return merged;
};
const buildSchema = mocks => {
  const schemaStr = print(schemaGQL) + clientDefs;
  const schema = makeExecutableSchema({
    typeDefs: schemaStr,
    resolverValidationOptions: {
      requireResolversForResolveType: false,
    },
  });

  return addMocksToSchema({ schema, mocks, preserveResolvers: true });
};

export const ApolloProviderWithMock = ({ customResolvers, client, children }) => {
  const mocks = mergeResolvers(globalMocks, customResolvers);
  const schema = buildSchema(mocks);

  let apolloClient = client;

  if (!client) {
    apolloClient = new ApolloClient({
      fetch,
      link: new SchemaLink({ schema }),
      cache: new InMemoryCache({ possibleTypes }),
      typeDefs: schema,
    });
  }

  return <ApolloProvider client={apolloClient}>{children}</ApolloProvider>;
};

Then in a test I just need to wrap the component being tested with it. Also, I can set specific values for specific queries

const customerResolver = {
  Query: () => ({
    SomeQuery: () => ({
      [...]
    })
  })
}
<ApolloProviderWithMock customerResolver={customerResolver}>
  <Component>
</ApolloProviderWithMock>

I am actually unable to get MockedProvider working at all on any 3.x version of @apollo/client.

import { render } from '@testing-library/react'
import '@testing-library/jest-dom/extend-expect'
import { MockedProvider } from '@apollo/client/testing'
import { Route, MemoryRouter } from 'react-router-dom'

...

describe('pipeline: none', () => {
  test('does not render environment section', async () => {
    const { getByText } = render(
      <MockedProvider
        mocks={[
          {
            request: {
              query: MARKETPLACE_OFFERING_QUERY,
              variables: {
                guid
              }
            },
            result: {
              data: {
                offering: { guid, name: 'mock offering' }
              }
            }
          }
        ]}
        addTypename={false}
      >
        <MemoryRouter initialEntries={['builder/123/456']}>
          <Route path="builder/:offering_guid/:plan_guid">
            <Builder />
          </Route>
        </MemoryRouter>
      </MockedProvider>
    )

    await new Promise(resolve => setTimeout(resolve, 0)) // wait for response

    expect(getByText('mock offering')).toBeInTheDocument()
  })
})

Leaving out this, the loading state is always true. With it the component is able to log out loading: true and then loading: false.

    await new Promise(resolve => setTimeout(resolve, 0)) // wait for response

The assertion fails, data is always undefined in the component even when loading: false from the useQuery.

My team seem to have run into the same problem :(

Same here, the mock provider seems to stopped working in some cases while running the styleguidist server, and when generating the static version of the styleguidist, some cases do work. A bit unstable, and now we are currently deciding if we should revert, or look for a solution.

In my case it was the same problem as described here: https://github.com/apollographql/apollo-client/issues/6771

I added logging to my component that did the mutations and saw that the variables contained a field with an explicit undefined instead of omitting the field. So I just explicitly added undefined for the variable in my mock as well and it started working!

e.g.
From:

    {
      request: {
        query: UPDATE_ITEM,
        variables: {
          teamId: 'team-1',
          itemId: 'item-1',
        },
    },

To:

    {
      request: {
        query: UPDATE_ITEM,
        variables: {
          teamId: 'team-1',
          itemId: 'item-1',
          optionalField: undefined
        },
    },

In my case I added the prop like that, <MockedProvider mocks={mock} addTypename={true}> and the error stopped. I realized that the error message was with the data with __typename. Like this:

products {
  name
  description
  price
  __typename
}

So I tried this and worked for me. However, the error still is a mystery for me.

Get this error - No more mocked responses for the query: mutation. Seems like MockedProvider broken or poorly documented.

I've resolved this with my post on this one

defaultOptions={{
                                watchQuery: { fetchPolicy: 'no-cache' },
                                query: { fetchPolicy: 'no-cache' },
                            }}

In my case it was the same problem as described here: #6771

I added logging to my component that did the mutations and saw that the variables contained a field with an explicit undefined instead of omitting the field. So I just explicitly added undefined for the variable in my mock as well and it started working!

I found the issue to be the same as @Markus-ipse

The change in behavior appears to have been introduced here, by switching to a different deep equal function: https://github.com/apollographql/apollo-client/commit/2593f8f62fbe87e5a3eebb8b44e0f5482a75c3f7#diff-fab0a81f7f7c1e746fd7f86a7a1458c9 and https://github.com/apollographql/apollo-client/commit/26849a804a1be65071818285c9c31de8a8e4fb13
(cc: @benjamn)

The previous isEqual implementation ignored undefined object properties. Unfortunately, we were relying on that behavior so hundreds of our tests are now failing in 3.1.x.

This behavior should be fixed/improved in @apollo/[email protected] (just published to npm), thanks to #7108. Please give it another try after updating!

Getting the same No more mocked responses for the query error.
Bumping to 3.3.0-beta.9 didn't resolve it.
Adding defaultOptions with fetchPolicy: "no-cache" didn't help either.

example

test:

describe("ProfileHeader", () => {
  it.only("show a profile header", async () => {
    const { container } = renderApollo(<ProfileHeader />, {
      defaultOptions: {
        query: { fetchPolicy: "no-cache" },
        watchQuery: { fetchPolicy: "no-cache" },
      },
    });
    await waitFor(() => {
      expect(container).toMatchSnapshot();
    });
  });

renderApollo

import { MockedProvider, MockedResponse } from "@apollo/client/testing";

const renderApollo = (
  node: any,
  {
    mocks,
    addTypename,
    defaultOptions,
    cache,
    resolvers,
    ...options
  }: RenderApolloOptions = {}
): RenderResult => {
  return render(
    <MockedProvider
      mocks={mocks}
      addTypename={addTypename}
      defaultOptions={defaultOptions}
      cache={cache}
      resolvers={resolvers}
    >
      {node}
    </MockedProvider>,
    options
  );
};

Component:

const ProfileHeader: React.FC = () => {
  const [queryExecutor, { loading, error, data }] = useLazyQuery<TMe>(
    PROFILE_QUERY,
    {
      // fetchPolicy: "cache-only", // <!== If changing the fetchPolicy here, the test pass!
      fetchPolicy: "cache-and-network",
      nextFetchPolicy: "cache-first",
    }
  );

It would seem like MockedProvider doesn't replace my useLazyQuery options with the defaultOptions passed from the test.
Just in case, I also tried to replace useLazyQuery with useQuery, and am also getting the same error

EDIT: My issue is unrelated to this thread, sorry for the noise
While I still don't know how we're supposed to write tests for queries using fetchPolicy: "cache-and-network" (I would very much like to test the different loading phases of my component), I could get my test to pass with a jest.spy

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
jest.spyOn(Apollo, "useLazyQuery").mockImplementation(() => {
  return [
    jest.fn(),
    {
      data: null,
      error: undefined,
      loading: false,
    },
  ];
});

Still experiencing this issue, even with setting fetchPolicy: no-cache, even with using the new beta version. The Dog example from the docs fails on my environment with direct copy/paste, "no more mocked responses". https://www.apollographql.com/docs/react/development-testing/testing/

worth noting that addTypename was set to FALSE but it's still expecting a __typename in one spot

Screen Shot 2020-10-15 at 9 39 59 AM

UPDATE:

I was getting these errors because I had missed the necessity of cleanup between test runs.

in create-react-app, I needed

import { cleanup, render } from '@testing-library/react';


afterEach(cleanup)

FURTHER UPDATE: cleanup was not sufficient, the same specs were intermittently getting this error in CI even when passing locally. It was necessary to version bump to the beta.

This behavior should be fixed/improved in @apollo/[email protected] (just published to npm), thanks to #7108. Please give it another try after updating!

This fixes my issue @benjamn , do we have any visibility into a release schedule? This is blocking our development and it'd be great to have an estimate. Thanks!

I was having this issue on any 3.x.x version (my mocks have correct variables + __typenames and all that junk, this seemed like a legitimate bug with MockedProvider), upgrading to @apollo/[email protected] did NOT fix it for me, but upgrading to @apollo/[email protected] DID fix it for me.

edit: just wanted to say that this didn't fix everything, still had some tests that were failing that definitely should pass (spent hours checking typenames + variables, 100% sure things are accurate). We had the most trouble with components that had 2 different queries running in the same file as well as queries with empty variables), it always got confused.
ended up doing this in our jest

jest.mock('@apollo/client', () => ({
...jest.requireActual('@apollo/client'),
useQuery: (query, options) => jest.requireActual('@apollo/client').useQuery(query, {
  ...options,
  fetchPolicy: 'no-cache'
  })
})
)



basically just forcing no-cache for testing purposes

The current apollo-client release is 3.3.5, and this seems to be fixed now.

Was this page helpful?
0 / 5 - 0 ratings