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] } }
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).
@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!
@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?
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?
Same here, it would really help to have at least an error logged that tell you why the result is not used.
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.
@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
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).
I'm having the same problem, tried everything in this thread and nothing works! Is there anything in the pipeline to help debug this?
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!
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!
@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.
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.
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!
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!
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
@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
MockedProviderare 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
debugNameas the key but I'm not sure how you'd provide that tographqlwithin 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!
@ahayes91 did you find a way to solve this?
@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!
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.
Most helpful comment
MockedProvider doesn't give you the mocked results when queries don't have
__typenamein 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 anApolloClientwith{ addTypename: false }Edit: to transform your queries to have typenames, you can do
import { addTypenameToDocument } from "apollo-client";and
addTypenameToDocument(query).