Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

Unable to get mockedData into props from MockedProvider in test #674

Closed
jasonmorita opened this issue May 1, 2017 · 20 comments
Closed

Unable to get mockedData into props from MockedProvider in test #674

jasonmorita opened this issue May 1, 2017 · 20 comments
Assignees
Labels
feature New addition or enhancement to existing solutions

Comments

@jasonmorita
Copy link

jasonmorita commented May 1, 2017

Following some ideas from this gist: https://gist.github.com/jbaxleyiii/b3634aeeab7bdb80ed4119ea5a07ba4a, I am unable to get a component test to work as expected.

Everything seems to work except the props never get populated with the data from the response.

Any reason why this isn't working as I expect it to?

Here is the simplest real-world example I could make based on an existing component:

// test-component.js
import React from 'react';
import { graphql } from 'react-apollo';
import userQuery from './userQuery.graphql';

function TestComponent (props) {
    console.log('props from component', props); // eslint-disable-line
    return <div>The component...</div>;
}

export default graphql(userQuery)(TestComponent);

// userQuery.graphql
query userQuery {
    user {
        items {
            firstName
            id
            lastName
        }
    }
}

// test-component-test.js
import React from 'react';
import { MockedProvider } from 'react-apollo/lib/test-utils';
import { mount } from 'enzyme';
import toJson from 'enzyme-to-json';
import userQuery from '../userQuery.graphql';
import { TestComponent } from '../';

const mockedData = {
    user: {
        items: [
            {
                firstName: 'userF',
                id: 'hhhh-gggg-iiii',
                lastName: 'userL',
            }
        ]
    }
};

describe('<TestComponent />', () => {
    describe('Markup should stay consistent', () => {
        const component = (
            <MockedProvider
                mocks={[
                    {
                        request: {
                            query: userQuery
                        },
                        result: { data: mockedData }
                    },
                ]}
                store={{
                    getState: () => {},
                    dispatch: () => {},
                    subscribe: () => {},
                }}
            >
                <TestComponent />
            </MockedProvider>
        );

        const mountedComponent = mount(component);

        it('should not have any changes without a new snapshot', () => {
            const tree = toJson(mountedComponent);
            expect(tree).toMatchSnapshot();
        });
    });
});

// console.log output during lifecycle
props from component { data:
 { variables: { offset: null, limit: null },
   refetch: [Function: bound ],
   fetchMore: [Function: bound ],
   updateQuery: [Function: bound ],
   startPolling: [Function: bound ],
   stopPolling: [Function: bound ],
   subscribeToMore: [Function: bound ],
   loading: true,
   networkStatus: 1,
   error: [Getter] } }

props from component { data:
 { variables: { offset: null, limit: null },
   refetch: [Function: bound ],
   fetchMore: [Function: bound ],
   updateQuery: [Function: bound ],
   startPolling: [Function: bound ],
   stopPolling: [Function: bound ],
   subscribeToMore: [Function: bound ],
   loading: false,
   networkStatus: 8,
   error: [Getter] } }
@jasonmorita jasonmorita changed the title Unable to get mockedData into props from MockedProvider in test Unable to get mockedData into props from MockedProvider in test May 1, 2017
@aryo
Copy link

aryo commented May 2, 2017

MockedProvider doesn't give you the mocked results when queries don't have __typename in them. (It auto adds typenames into the query as the mock store key, so when the original query doesn't have them, it won't find a matching query.)

You can either add __typenames into your queries or make your own MockedProvider that passes in an ApolloClient with { addTypename: false }

Edit: to transform your queries to have typenames, you can do
import { addTypenameToDocument } from "apollo-client";
and addTypenameToDocument(query).

@jbaxleyiii
Copy link
Contributor

@aryo thanks for the response! That is indeed most likely the issue. However it stinks that you have to do that. I'm going to be working on testing utils and improving what is there so this should be easier / documented soon!

@jbaxleyiii jbaxleyiii self-assigned this May 3, 2017
@jbaxleyiii jbaxleyiii added enhancement feature New addition or enhancement to existing solutions labels May 3, 2017
@michaelarick
Copy link

@aryo @jbaxleyiii I'm having a similar issue to the one described here (but I know there is a match in the provider, including __typename). We have a test that works, using a mockNetworkInterfaceWithSchema, but when I adapt that to a MockedProvider wrapping our Component, the Component doesn't receive data. Any chance there is a bug here?

@visualskyrim
Copy link

visualskyrim commented May 16, 2017

Same here. I tried the gist above and it didn't work.
The porps received in the Foo component's componentWillReceiveProps has a loading equaling to false, but there is no mocked data in it.
I print all the keys in the props.data in componentWillReceiveProps:

variables,refetch,fetchMore,updateQuery,startPolling,stopPolling,subscribeToMore,loading,networkStatus,error

And the error in the props is undefined.

Any idea?

@eskimoblood
Copy link

Same here, it would really help to have at least an error logged that tell you why the result is not used.

@aryo
Copy link

aryo commented May 27, 2017

Not sure if this will help, but I've found that if the shape of the mocked data doesn't conform to the query (e.g. the mocked data is missing a field that is requested in the query, or is missing a typename), then the component's props won't get populated with the data.

It just silently fails without any error.

@eskimoblood
Copy link

@aryo that the same for use, the problem is that there is no error message and we cant debug it cause we have compiled apollo code

@bartlett705
Copy link

bartlett705 commented Jun 9, 2017

I noticed something unexpected when I was debugging <MockedProvider> tests yesterday that may relate to the issue @eskimoblood or @michaelarick are having:

If you have variables in your query, the ORDER they are declared in your actual request and your mocked request object must match. I spent a lot of time banging my head against this, because ECMAScript usually doesn't care about object property order.

Just to be 100% clear what I mean, if your mock looks like this:

const mock = {
  request: {
    query: addTypenameToDocument(gameQuery),
    variables: {
      name: 'Hearthstone',
      limit: 30,
    },
  },
  result: {
    data: { ... }
  }
}

...and in the apollo-connected component, you make this query:

@graphql(gameQuery, {
  options: (props: Props) => ({
    variables: {
      limit: 30,
      name: 'Hearthstone',
    },
  })
}

...the mock will not be returned. I feel like this is an unexpected behavior, and this is the most relevant discussion I could find on the subject. I think this arises from the way the mocking HOC stores and compares queries (using a serialized version of the query object).

@hamishtaplin
Copy link

I'm having the same problem, tried everything in this thread and nothing works! Is there anything in the pipeline to help debug this?

@stale
Copy link

stale bot commented Jul 5, 2017

This issue has been automatically marked as stale becuase it has not had recent activity. It will be closed if not further activity occurs. Thank you for your contributions to React Apollo!

@stale
Copy link

stale bot commented Jul 29, 2017

This issue has been automatically labled because it has not had recent activity. If you have not received a response from anyone, please mention the repository maintainer (most likely @jbaxleyiii). It will be closed if no further activity occurs. Thank you for your contributions to React Apollo!

@L1fescape
Copy link
Contributor

@bartlett705 actually the entire request object in your mocks needs to exactly match the request being made in your code, including the order of variables. The mock objects you provide to MockedProvider are stringified and used as keys when registering and looking up the response object that should be returned when a request is made within the component you're testing. I just discovered this today debugging a test that wasn't getting the correct data. The docs probably need a section for testing that describes this behavior because it's not expected.

I wonder if there's an alternative to stringifying the entire request object. Maybe you could use debugName as the key but I'm not sure how you'd provide that to graphql within your component so it can make the request with that key included.

@jasonmorita
Copy link
Author

What I ended up doing was exporting the unwrapped component and passing in all the props with any functions as jest.fn()s and any components that needed external wrappers as mocks as well.

This allows me to test the component itself without needing to worry about trying to mock all the Apollo-related things.

Any of the functions that you need to supply to Apollo, I have placed in a separate file. These are all factory functions which return the various options, props(), update(), etc. functions that are used to set up your graphql HOC. Having this set up allows all of those functions to be tested in isolation as well.

The gist is, my team is testing the components unwrapped, which removes the need to try to set up mock requests.

@stale
Copy link

stale bot commented Sep 9, 2017

This issue has been automatically labled because it has not had recent activity. If you have not received a response from anyone, please mention the repository maintainer (most likely @jbaxleyiii). It will be closed if no further activity occurs. Thank you for your contributions to React Apollo!

@stale stale bot closed this as completed Sep 23, 2017
@stale
Copy link

stale bot commented Sep 23, 2017

This issue has been automatically closed because it has not had recent activity after being marked as no recent activyt. If you belive this issue is still a problem or should be reopened, please reopen it! Thank you for your contributions to React Apollo!

@craigbilner
Copy link

craigbilner commented Oct 2, 2017

For anyone who ends up here, I followed the example app "exactly" but I didn't add __typename to my mocked data. It does now warn (if it didn't before?) about any missing fields in the console upon writing to the store e.g.

Missing field __typename in {
  "name": "john-smith"
}

Just any random __typename passes the truthy check and then the data should come through

Alternatively: just set removeTypename to true on your MockedProvider

@ahayes91
Copy link

ahayes91 commented Jun 6, 2019

@bartlett705 actually the entire request object in your mocks needs to exactly match the request being made in your code, including the order of variables. The mock objects you provide to MockedProvider are stringified and used as keys when registering and looking up the response object that should be returned when a request is made within the component you're testing. I just discovered this today debugging a test that wasn't getting the correct data. The docs probably need a section for testing that describes this behavior because it's not expected.

I wonder if there's an alternative to stringifying the entire request object. Maybe you could use debugName as the key but I'm not sure how you'd provide that to graphql within your component so it can make the request with that key included.

If this is the case, then that means we can't mock and test what should happen if you have an empty array in your returned data. To illustrate with an example (the syntax may be slightly off but hopefully you'll get the gist):

GraphQL query:

export const GET_PAGINATED_DATA = gql`
  query(
    $limit: Int!
    $offset: Int!
  ) {
    thingWithItems {
      paginatedItems(
        limit: $limit
        offset: $offset
      ) {
        total
        items {
          refId
          name
        }
       }
      }
     }
`;

And you use a mock response with an array of items:

export const MOCK_RESPONSE = {
  data: {
    thingWithItems: {
      paginatedItems: {
        total: 2,
        items: [
           {
            refId: 'item1',
            name: 'thing1',
           },
           {
            refId: 'item2',
            name: 'thing2',
           },
         ],
      },
    },
  },
  errors: [],
  extensions: null,
};

But if you use a mock response for no items:

export const MOCK_RESPONSE_NO_ITEMS = {
  data: {
    thingWithItems: {
      paginatedItems: {
        total: 0,
        items: [],
      },
    },
  },
  errors: [],
  extensions: null,
};

This will give an error (Error: Network error: No more mocked responses for the query:) because the empty items array doesn't contain the fields you specified in your query.
But what if you have some client-side logic that renders a specific react component based on if isEmpty(data.thingWithItems.paginatedItems.items) is true? You can't test that branch of logic. Or is there a way to get around this?

Any pointers on how to get around this would be great!

@pvdlg
Copy link

pvdlg commented Feb 10, 2020

@ahayes91 did you find a way to solve this?

@ahayes91
Copy link

ahayes91 commented Feb 11, 2020

@ahayes91 did you find a way to solve this?

@pvdlg I raised a separate issue for it, and then closed it after an eagle-eyed colleague spotted that I had accidentally put MOCK_RESPONSE in my mock query instead of MOCK_VARIABLES. My successful test looks something like this:

const GET_PAGINATED_DATA = gql`
  query(
    $limit: Int!
    $offset: Int!
  ) {
    thingWithItems {
      paginatedItems(
        limit: $limit
        offset: $offset
      ) {
        total
        items {
          refId
          name
        }
       }
      }
     }
`;

const MOCK_RESPONSE_NO_ITEMS = {
  data: {
    thingWithItems: {
      paginatedItems: {
        total: 0,
        items: [],
      },
    },
  },
  errors: [],
  extensions: null,
};

const MOCK_VARIABLES = {
  limit: 10,
  offset: 0,
};

const MOCK_PROPS = {
  offset: MOCK_VARIABLES.offset,
};

const APOLLO_MOCK_NO_ITEMS = [
  {
    request: {
      query: GET_PAGINATED_DATA,
      variables: MOCK_VARIABLES,
    },
    result: MOCK_RESPONSE_NO_ITEMS,
  },
];

it('should render TestMessage when data has no items', async () => {
    const container = mount(
          <MockedProvider mocks={APOLLO_MOCK_NO_ITEMS}>
            <MyQuery {...MOCK_PROPS} />
          </MockedProvider>,
    );

    await wait(() => {
      container.update();
      const noItemsMessage = container.find(TestMessage);
      expect(noItemsMessage.exists()).toEqual(true);
    });
  });

Hope that helps!

@raymondshiner
Copy link

Not sure if this will help, but I've found that if the shape of the mocked data doesn't conform to the query (e.g. the mocked data is missing a field that is requested in the query, or is missing a typename), then the component's props won't get populated with the data.

It just silently fails without any error.

I found that this was the problem for me, apparently the contract of mocked provider requires you to have the response object match the query? It makes enough sense but there is nothing that says that in the docs (if i am wrong about that please let me know where it is, i couldn't find it!).

This is should really be something that is in the apollo documentation for mocked provider. wasted a number of hours trying to find this.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
feature New addition or enhancement to existing solutions
Projects
None yet
Development

No branches or pull requests