enzyme icon indicating copy to clipboard operation
enzyme copied to clipboard

Apollo MockedProvider with Enzyme Shallow Rendering

Open jvanbaarsen opened this issue 4 years ago • 17 comments

After spending a couple of days and looking all over the internet I'm starting to doubt what I'm doing wrong 🙈

I'm trying to test a component that uses useQuery with Enzyme's Shallow rendering method: https://github.com/jvanbaarsen/apollo-enzyme-test/blob/master/src/App.js#L17.

Somehow, whatever I try I keep getting the error that a client is not available and that I need to wrap my component in a ApolloProvider. I tried solving this by using a MockedProvider: https://github.com/jvanbaarsen/apollo-enzyme-test/blob/master/src/App.test.js#L19.

But no matter what I try, it just doesn't work with Shallow. When I try to use Mount it works, but I'd like to test a component in isolation as much as possible.

Is this something I'm doing wrong? Or just not support by shallow rendering?

A full test repo can be found here: https://github.com/jvanbaarsen/apollo-enzyme-test.

I'm cross-posting this issue also on the apollo-client repo, since I'm not really sure if this is a Enzyme or Apollo question.

jvanbaarsen avatar Apr 10 '20 09:04 jvanbaarsen

Use the wrappingComponent option to pass the Provider, instead of passing it directly inside shallow.

ljharb avatar Apr 12 '20 20:04 ljharb

Same issue here - I am passing in wrappingComponent option, just like it's documented and just like @jvanbaarsen is passing it in as well - but with no luck. Works with mount but fails with shallow

vdineva avatar Apr 12 '20 21:04 vdineva

@vdineva would you mind opening up a PR with a failing test case?

ljharb avatar Apr 13 '20 06:04 ljharb

@ljharb Would it be sufficient for you if I add a failing test case to my example repo?

jvanbaarsen avatar Apr 13 '20 06:04 jvanbaarsen

@jvanbaarsen i mean, i'll take it if that's all you can offer :-D but the real thing i need is a test case in this repo, so i can debug against it.

ljharb avatar Apr 13 '20 06:04 ljharb

@ljharb Allright, lets start with the test case in my repo, and I'll do my best to come up with a test case in this repo.

Here is the test code in my repo: https://github.com/jvanbaarsen/apollo-enzyme-test/commit/04c9be5cf4e021f93538b97702b448ef098c6ef7#diff-abcd7b0d7cb054dcd47fded339bbbf73R68

Thanks for your help 💛

jvanbaarsen avatar Apr 13 '20 06:04 jvanbaarsen

@ljharb Ok, here is where my limited JS eco system knowledge comes into play haha. How would one go about creating such a test case? That would require me to add Apollo to the mix to create a test case that uses the Apollo hooks right? or is there an easier way?

jvanbaarsen avatar Apr 13 '20 06:04 jvanbaarsen

+1 I'm having the same issue, passing in wrappingComponent option the MockedProvider imported from @apollo/react-testing and is not working because I'm getting could not find client error.

MathiasMinacapilli avatar Apr 15 '20 15:04 MathiasMinacapilli

+1 I'm having the same issue, passing in wrappingComponent option the MockedProvider imported from @apollo/react-testing and is not working because I'm getting could not find client error.

Same for me. If I directly use:

        const wrapper = shallow(
        <MockedProvider mocks={[mocks]} addTypename={false}>
            <MyComponent />
        </MockedProvider>
        )

I cannot get any child components to test. ( I am using Material UI Select and should get MenuItem and some texts... Since I have tried without queries )

But if I am using wrappingComponent it will give no client error.

My settings for wrappingComponent is like this:

        function MyProvider(props) {

            return (
              <MockedProvider mocks={[mocks]} addTypename={false}>
                  {props.children}
              </MockedProvider>
            );
          }

          MyProvider.propTypes = {
            children: PropTypes.node
          };

        const wrapper = shallow(
            <MyComponent />,
          {
            wrappingComponent: MyProvider,
          }
        )

Thanks for any help!

flora8984461 avatar Jun 18 '20 16:06 flora8984461

Hi @flora8984461 , with shallow function I could get to wrap with MockedProvider. But try with mount, there should work. I know that's a less efficient and performant way, but it should work with mount.

Good luck!

MathiasMinacapilli avatar Jun 26 '20 12:06 MathiasMinacapilli

both shallow and mount is working for me, the only problem is the mock returned value is undefined but it maps my list with the same length as my mock result data.

const setup = (): ShallowWrapper =>
  shallow(
    <MockedProvider mocks={mocks} addTypename={false}>
      <Apollo data-test-id="apollo-component" />
    </MockedProvider>,
  );

describe('<Apollo /> Component', () => {
  let wrapper: ShallowWrapper;

  beforeEach(() => {
    wrapper = setup();
  });

  describe('Renders', () => {
    it('should render without crashing', async () => {
      await wait(wrapper);
      const component = findByTestId(wrapper, 'apollo-component');
      expect(component.length).toBe(1);
    });
  });
});

ssengalanto avatar Jul 03 '20 14:07 ssengalanto

Its working for me now I just need to add fragment matcher to resolve undefined results. but here's how I initialize MockedProvider. (it also works with shallow)

render-apollo.utils.ts

import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import { MockedProvider } from '@apollo/client/testing';
import { MockedProviderProps } from '@apollo/client/utilities/testing/mocking/MockedProvider';

export const renderApollo = ({
  children,
  addTypename = false,
  ...props
}: MockedProviderProps): ReactWrapper =>
  mount(
    <MockedProvider addTypename={addTypename} {...props}>
      {children}
    </MockedProvider>,
  );

mocked-apollo-props.type.ts

import { MockedProviderProps } from '@apollo/client/utilities/testing/mocking/MockedProvider';

export type MockedApolloProps = Omit<MockedProviderProps, 'children'>;

initialize MockedProvider

const mocks = [ { ... } ]

const setup = (props: MockedApolloProps = { mocks }): ReactWrapper =>
  renderApollo({
    children: <YourReactComponent />,
    ...props,
  });

ssengalanto avatar Jul 04 '20 08:07 ssengalanto

Its working for me now I just need to add fragment matcher to resolve undefined results. but here's how I initialize MockedProvider. (it also works with shallow)

render-apollo.utils.ts

import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import { MockedProvider } from '@apollo/client/testing';
import { MockedProviderProps } from '@apollo/client/utilities/testing/mocking/MockedProvider';

export const renderApollo = ({
  children,
  addTypename = false,
  ...props
}: MockedProviderProps): ReactWrapper =>
  mount(
    <MockedProvider addTypename={addTypename} {...props}>
      {children}
    </MockedProvider>,
  );

mocked-apollo-props.type.ts

import { MockedProviderProps } from '@apollo/client/utilities/testing/mocking/MockedProvider';

export type MockedApolloProps = Omit<MockedProviderProps, 'children'>;

initialize MockedProvider

const mocks = [ { ... } ]

const setup = (props: MockedApolloProps = { mocks }): ReactWrapper =>
  renderApollo({
    children: <YourReactComponent />,
    ...props,
  });

Thanks @ssengalanto and @MathiasMinacapilli . This might be a good way. But I have not tried with it since later we switch to put useQuery and useMutation to another component file and just pass the mock data and functions to test the required components.

flora8984461 avatar Jul 07 '20 21:07 flora8984461

Here's another (ugly) workaround:

const client = new ApolloClient({
  cache: new InMemoryCache({ addTypename: false }),
  link: new MockLink(mocks, false),
})

Object.defineProperty(React, Symbol.for('__APOLLO_CONTEXT__'), {
  value: React.createContext<ApolloContextValue>({
    client,
  }),
  enumerable: false,
  configurable: true,
  writable: false,
})

const component = shallow(<MyComponent />)

ldiqual avatar Aug 18 '20 18:08 ldiqual

Here's another (ugly) workaround:

const client = new ApolloClient({
  cache: new InMemoryCache({ addTypename: false }),
  link: new MockLink(mocks, false),
})

Object.defineProperty(React, Symbol.for('__APOLLO_CONTEXT__'), {
  value: React.createContext<ApolloContextValue>({
    client,
  }),
  enumerable: false,
  configurable: true,
  writable: false,
})

const component = shallow(<MyComponent />)

Just tried that workaround, unfortunately, it does not work.

No updates on this issue?

delucca avatar Feb 04 '21 13:02 delucca

There is my ugly workaround to the issue

jest.mock('@apollo/client', () => ({
  ...jest.requireActual('@apollo/client'),
  useQuery: jest.fn(),
  useMutation: jest.fn(),
}));
const { useQuery, useMutation } = require('@apollo/client');

export const renderWithApolloClient = (
  Component
) => {
  const wrapper = shallow(
    <MockedProvider mocks={mocks} addTypename={false}>
      {Component}
    </MockedProvider>
  );
  const { client } = wrapper.props();
  const { useQuery: useOrigQuery, useMutation: useOrigMutation } =
    jest.requireActual('@apollo/client');
  useQuery.mockImplementation((q, options) =>
    useOrigQuery(q, { ...options, client })
  );
  useMutation.mockImplementation((m, options) =>
    useOrigMutation(m, { ...options, client })
  );
  return wrapper.shallow();
}

In my case to get actual Component shallow wrapper instance I have to do .shallow() four times.

makser avatar Sep 03 '21 11:09 makser

@masker if you have 4 HOCs, you're expected to do 4 .dive()s (.shallow() works also)

ljharb avatar Sep 03 '21 15:09 ljharb