apollographql / react-apollo

:recycle: React integration for Apollo Client
https://www.apollographql.com/docs/react/
MIT License
6.85k stars 790 forks source link

writeQuery doesn't work in unit tests on useMutation #3689

Open muhraff opened 4 years ago

muhraff commented 4 years ago

Intended outcome:

I have a mutation with an update option which calls cache.writeQuery.

This is updating the cache and re-rendering queries as expected when we run in the browser.

In unit tests for that component, the cache is not updating when cache.writeQuery is called.

Container.js

const customerAddressesQuery = gql`...`
const customerAddressDelete = gql`...`

const Container = () => {
 const [deleteAddress] = useMutation(customerAddressDelete, {
    update: (cache, { data }) => {
      const { deletedAddressId } = data.customerAddressDelete
      const { customer } = cache.readQuery({ query: customerAddressesQuery })

      cache.writeQuery({
        query: customerAddressesQuery,
        data: {
          customer: {
            ...customer,
            addresses: {
              ...customer.addresses,
              edges: customer.addresses.edges.filter(
                item => item.node.id !== deletedAddressId
              ),
            },
          },
        },
      })
    },
  })

  return <Component deleteAddress={deleteAddress} />
})

Container.test.js

import { render, waitForElement, waitForElementToBeRemoved, fireEvent  } from '@testing-library/react'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { MockedProvider } from '@apollo/react-testing'
import Container from '../Container'

test('should able to delete the address', async () => {
    const MOCKS = [
      {
        request: {
          query: customerAddressesQuery,
        },
        result: {
          data: {
            customer: {
              id: 'customer_id_0001',
              addresses: {
                edges: [
                  {
                    node: {
                      company: 'company name',
                      id: 'address_id_0001',
                      name: 'Name A',
                      phone: '+0000000000',
                      formatted: ['address', 'apartment', 'city', 'country'],
                      __typename: 'MailingAddress',
                    },
                    __typename: 'MailingAddressEdge',
                  },
                ],
                __typename: 'MailingAddressConnection',
              },
              defaultAddress: {
                id: 'address_id_0001',
                __typename: 'MailingAddress',
              },
              __typename: 'Customer',
            },
          },
        },
      },
      {
        request: {
          query: customerAddressDelete,
          variables: {
            addressId: 'address_id_0001',
          },
        },
        result: {
          data: {
            customerAddressDelete: {
              customerUserErrors: [],
              deletedCustomerAddressId: 'address_id_0001',
              __typename: 'CustomerAddressDeletePayload',
            },
          },
        },
      },
    ]

    const cache = new InMemoryCache()

    const { getByTestId, getByText } = render(
      <MockedProvider cache={cache} mocks={MOCKS}>
        <Container />
      </MockedProvider>
    )

    const deleteButton = await waitForElement(() => getByTestId('delete-button'))

    fireEvent.click(deleteButton)

    await waitForElementToBeRemoved(() => getByText('Name A'))
   // <------ Test fails here because element is not removed

    expect(getByText('Name A')).toBeNull()
  })

Actual outcome:

How to reproduce the issue:

Log the entire cache to the console before and after writeQuery is called. Value never changes.

Version

@testing-library/react@9.3.2
@apollo/react-hooks@3.1.3
apollo-cache-inmemory@1.6.3
@apollo/react-testing@3.1.3
korgara commented 4 years ago

@muhraff hi, did you fix this?

muhraff commented 4 years ago

@korgara Unfortunately No.

dmt0 commented 4 years ago

In my case, as I inspect cache in the debugger, readQuery and writeQuery are not defined at all. I'm trying to mock them out in my tests, without success so far.

HTMLhead commented 4 years ago

@dmt0 Thank you for the clue. I can solve my problem with this code

I'm using react-testing-library

const renderWithProvider = async (mocks, cache) => 
  <MockedProvider {...{ mocks }} cache={cache} addTypename={false}>
    <MyComponent />
  </MockedProvider>

it("example test case.", async () => {
  const cache = new InMemoryCache({ addTypename: false });
  const utils = await renderWithProvider(cache);

  // Doing call mutation with writeQuery
  // Don't forget to check the mutation is done

  const afterMutationResult = cache.readQuery({
    query: {WRITED_QUERY_HERE}
  });

  // then, the 'afterMutationResult' has data after writeQuery
  expect(afterMutationResult).toStrictEqual(whatYouExpecting);
});
dmt0 commented 4 years ago

I ended up solving this like so:

export const mockCache = () => {
  const map = new Map();
  return {
    size: () => map.size,
    readQuery: ({query, variables}) =>
      map.get(query + JSON.stringify(variables)),
    writeQuery: ({query, variables, data}) =>
      map.set(query + JSON.stringify(variables), data),
  };
};

const cache = new InMemoryCache();
Object.assign(cache, mockCache());