Testing with React Apollo ignores variables - reactjs

I have fairly simple test setup very similar to what's coming with tutorial repo from Apollo GraphQL and described in their documentation.
Here's the query:
const GET_USER_WITH_SPEND_CONTROL = gql`
query GetUserWithSpendControl($userToken: ID!) {
me(token: $userToken) {
firstName
lastName
spendControl {
amountAvailable
currencyCode
}
}
}
`;
Here's the test:
const mockUserSpendControl = {
me: {
firstName: "Andrii",
lastName: "Gorishnii",
spendControl: {
amountAvailable: 1000,
currencyCode: "USD",
}
}
};
describe('Account Summary', () => {
afterEach(cleanup);
it('renders account summary', async () => {
const mocks = [
{
request: { query: GET_USER_WITH_SPEND_CONTROL },
result: {
data: mockUserSpendControl
},
},
];
const { getByText } = await renderApollo(<AccountSummary/>, {
mocks,
cache,
addTypename: false
});
await waitForElement(() => getByText(/Gorishnii/i));
});
});
The wrong thing about this test is that it works while it shouldn't: GQL schema declares required variable userToken however if I add it to the test like this:
...
request: { query: GET_USER_WITH_SPEND_CONTROL, variables: {userToken: "56aed"} },
...
It actually fails the test.
Does anyone have similar problems with testing React Apollo components?
P.S. I'm having bigger problem where my original query goes like this:
const GET_USER_WITH_SPEND_CONTROL = gql`
query GetUserWithSpendControl($userToken: ID!) {
currentUserToken #client #export(as: "userToken")
me(token: $userToken) {
firstName
lastName
spendControl {
amountAvailable
currencyCode
}
}
}
`;
And I can never match the mocked result with the query which is using variables exported from client's cache. But this will probably be next question after I solve "simpler" problem.

Related

GraphQL / Relay - optimisticResponse isn't propagating as I had hoped

In the mutation below I'm updating the avatarId for a person and I would like the new imageUrl to propagate everywhere it's needed, namely the <UserAvatar /> component which can be in a lot of components on the same page.
I know this new URL before ever hitting the UpdatePersonMutation so I'm not sure what I'm doing wrong.
Do I need to involve the store and / or use optimisticUpdater, and / or use subscriptions to get a new avatar image to show up everywhere instantly?
import { commitMutation } from 'react-relay';
import graphql from 'babel-plugin-relay/macro';
import modernEnvironment from '../Environment';
const mutation = graphql`
mutation UpdatePersonMutation($input: UpdatePersonInput!) {
updatePerson(input: $input) {
imageByAvatarId {
id
imageUrl // I know this before I ever reach this mutation
}
person {
id
avatarId
}
}
}
`;
const commit = (payload, callback) => {
const input = {
id: payload.id,
personPatch: {
avatarId: payload.avatarId,
},
};
return commitMutation(modernEnvironment, {
mutation,
variables: {
input,
},
optimisticResponse: {
updatePerson: {
imageByAvatarId: {
id: payload.imageId,
imageUrl: payload.imageUrl,
},
person: {
id: payload.id,
avatarId: payload.avatarId,
},
},
},
onCompleted: response => {
callback(response);
},
onError: error => {
console.log(error);
},
});
};
export default commit;

How to reset or clear the useQuery mocks of ApolloProvider before each single test?

I am having in one describe two test functions. They both use a component with apollo mocks as this component uses useQuery. But the response from mocks for each test should be different. Now I am unable to reset or clear mocks which I propagate to the ApolloProvider. Has anyone had this kind of issue? How did you solve it?
import React from 'react'
import { render, createMocks, screen, cleanup } from 'test-utils'
import GET_DEVICES from 'src/screens/dashboard/queries/GET_DEVICES'
import Section from './Section'
describe('DashboardScreen', () => {
const result = {
data: {
numDevices: 1
}
}
const mocks = createMocks([{
query: GET_DEVICES,
result: result,
}])
afterEach(() => {
jest.clearAllMocks() <------ THIS IS NOT SOLVING THE ISSUE
})
it('should load Section with expected badges', async () => {
render(<Section />, { mocks })
const badge0 = screen.getByTestId('Badge-0')
expect(badge0).toBeInTheDocument()
const badge1 = screen.getByTestId('Badge-1')
expect(badge1).toBeInTheDocument()
})
it('should display the exclamation icon and 7 devices require attention', async () => {
const result_local = {
data: {
numDevices: 4,
}
}
const mocks_local = createMocks([
{
query: GET_DEVICES,
result: result_local,
}
])
render(<Section } />, { mocks: mocks_local })
const devicesWithActiveIssuesIcon = await screen.findByTestId('ExclamationIcon')
expect(devicesWithActiveIssuesIcon).toBeInTheDocument()
})
})
The createMocks is an import and looks like this:
const createMocks = mocks =>
mocks.reduce(
(requests, mock) => [
...requests,
{
request: {
query: mock.query,
variables: mock.variables
},
result: mock.result
}
],
[]
)
It will return an object as follows:
const mocks = [
{
request: {
query: GET_DEVICES,
variables: {}
},
result: {
data: {
numDevices: 1
},
},
},
]
the issue might be from that the ApolloProviders used to render this two components are sharing the same InMemoryCache instance. see the following link for details.
https://github.com/benawad/apollo-mocked-provider/issues/1#issuecomment-511593398
if this is the case you just need to create new instance of InMemoryCache for every test.

Query data doesn't update after successful apollo cache write

I have a query on my App.js:
import { gql } from 'apollo-boost';
const ALL_ITEMS_QUERY = gql`
query ALL_ITEMS_QUERY {
challenges {
id
title
}
goals {
id
title
completed
createdAt
updatedAt
steps {
id
completed
title
}
}
}
`;
And i am looking to write a simple deleteGoal mutation:
const DeleteWrapper = (props) => {
const [deleteGoal, { data }] = useMutation(DELETE_ITEM_MUTATION, {
update(cache, payload) {
const data = cache.readQuery({ query: ALL_ITEMS_QUERY });
data.goals = data.goals.filter(
(goal) => goal.id !== payload.data.deleteGoal.id
);
cache.writeQuery({ query: ALL_ITEMS_QUERY, data });
},
});
}
The function returns the modified array correctly, but the item never disappears from the frontend list. I have a hunch that this is related to querying multiple categories at once (goals and challenges, rather than goals only).
Even though the cache seems to be modified correclty, why does the item never disappear, why does the re-render never happen?
After some trial and error I found out that I have to lay out the exact data object to the writeQuery function. I don't really understand why, since the challenges object was left untouched after the query. I have not been able to make this work otherwise.
const DeleteWrapper = (props) => {
const [deleteGoal] = useMutation(DELETE_ITEM_MUTATION, {
update(cache, { data: { deleteGoal} }) {
const { goals, challenges } = cache.readQuery({ query: ALL_ITEMS_QUERY });
const newArr = goals.filter((goal) => goal.id !== deleteGoal.id);
cache.writeQuery({
query: ALL_ITEMS_QUERY,
data: { challenges, goals: newArr },
});
},
});
}

Is there a way to mock refetch in MockedProvider - Apollo Client?

Here is how I am using MockedProvider. How can I mock refetch in mocks array?
const mocks = [{
request: {
query: GET_USERS_BY_FACILITY,
variables: {
facility: 300
}
},
result: {
data: {
GetUsersByFacility: [{
nuId: 'Q916983',
userName: faker.internet.userName(),
profileKey: 'testKey',
profileValue: 'testValue',
__typename: 'FacilityUser'
}]
}
},
refetch: () => {
return {
data: {
GetUsersByFacility: [{
nuId: 'Q916983',
userName: faker.internet.userName(),
profileKey: 'testKey',
profileValue: 'testValue',
__typename: 'FacilityUser'
}]
}
}
}
}
This test case calls refetch function when delete event is triggered.
it('should be able to click on delete user', async () => {
const {getByTestId} = render(
<MockedProvider mocks={mocks}>
<Users selectedFacility={300}/>
</MockedProvider>)
await wait(0)
fireEvent.click(getByTestId('btnDelete'))
})
I have been trying different ways, none seems to work. I get error message as TypeError: Cannot read property 'refetch' of undefined.
Thank you very much in hope of an answer.
Regards,
--Rajani
Maybe it's a bit late to answer, but if you have't got any answers yet, you would refer to the way I solved.
Please note that this might not be the correct answer.
You can find this code in react-apollo docs
const mocks = [
{
request: {
query: GET_DOG_QUERY,
variables: {
name: 'Buck',
},
},
result: () => {
// do something, such as recording that this function has been called
// ...
return {
data: {
dog: { id: '1', name: 'Buck', breed: 'bulldog' },
},
}
},
},
];
I make my refetch testcode based on this phrase // do something, such as recording that this function has been called
This is my mock example.
let queryCalled = false
const testingData = (value) => ({
data: {....}
})
const TESTING_MOCK = {
request: {
query: TESTING_QUERY,
variables: { some: "variables" },
},
result: () => {
if (queryCalled) return testingData("refetched");
else {
queryCalled = true;
return testingData("first fetched");
}
},
};
This component refetches data when the button is clicked. I designed my test code in this order
when it's rendered for the first time, it fetches the mock data .
=> In the code above, queryCalled is false so, it reassigns queryCalled as true and return the "first fetched" mock data,
when a click event occurs, refetch occurs too.
=> On the same principle the mock data returns "refetched" mock data.
My testcode example is here
it("refetch when clicked save button.", async () => {
const mocks = [TESTING_MOCK];
let utils: RenderResult = render(<SomeTestingComponent mocks={mocks} />);
await waitForNextTick(); //for getting a data, not loading
const titleInput = utils.getByDisplayValue("first fetched");
const saveBtn = utils.getByText("save");
fireEvent.click(saveBtn);
await waitForElement(() => utils.getByDisplayValue("refetched"));
})
Please let me know if you have any other suggestions!
For anyone that might still run into this, the solution to make refetch work in your tests is to use the newData method while keeping track of the query having been called.
I don't know if this is a bug in the MockedProvider implementation, but I was banging my head against the wall trying to make newData work together with result, but it turns out that newData completely overrides result.
A working solution (tested with useQuery and Apollo Client 3) would be something like this:
let queryCalled = false;
const refetchMock = {
request: {
query: YOUR_QUERY
},
newData: () => {
if (queryCalled) {
return {
data: {
// your refetched data
}
};
} else {
queryCalled = true;
return {
data: {
// your first fetched data
}
};
}
}
};
The newData solution didn't work for me with apollo client #2.6.
As a workaround, for the few tests that utilize refetch I had to physically mock the useQuery function and provide mock functions for the return of refetch; for our custom hook (where an overridden useQuery hook is exported as default), it looked something like this:
import * as useQueryModule from '~/hooks/useQuery';
describe('test the thing', () => {
let useQuerySpy;
beforeEach(() => {
// Spy on the `useQuery` function so we can optionally provide alternate behaviour.
useQuerySpy = jest.spyOn(useQueryModule, 'default');
})
afterEach(() => {
// Restore any mocked behaviour
useQuerySpy.mockRestore();
});
it('does a thing', async () => {
const refetchedApolloResponse = buildResponse(refetchData) // some function to build the shape / data of apollo response
const initialApolloResponse = buildResponse(initialData) // some function to build the shape / data of apollo response
const mockRefetch = jest.fn().mockResolvedValue({ data: refetchedApolloResponse });
useQuerySpy.mockReturnValue({ data: initialApolloResponse, refetch: mockRefetch });
// Assert stuff
}
})
This solution did not work for me and not sure whether it will work or not because it didn't work in my case.
let queryCalled = false
const testingData = (value) => ({
data: {....}
})
const TESTING_MOCK = {
request: {
query: TESTING_QUERY,
variables: { some: "variables" },
},
result: () => {
if (queryCalled) return testingData("refetched");
else {
queryCalled = true;
return testingData("first fetched");
}
},
};
So I solved it by another way which is:
const mocks = [
{
request: {
query: GET_DOG_QUERY,
variables: {
name: 'Buck',
},
},
result: {
data: {
dog: { id: '1', name: 'Buck', breed: 'bulldog' },
},
},
newData: jest.fn(() => ({
data: {
dog: { id: '1', name: 'Refetched-Buck', breed: 'refetched-bulldog' },
},
})),
},
];
It worked like a charm for me.

Writing tests for React and Apollo

I'm trying to write a unit test from React with Apollo.
I found an example from https://dev-blog.apollodata.com/seamless-integration-for-graphql-and-react-6ffc0ad3fead
When trying that out I’m getting an error:
Error:
Uncaught (in react-apollo) Error: Network error: No more mocked responses for the query: query people {
allPeople(first: 1) {
people {
name
__typename
}
__typename
}
}
Test:
it('executes a query', (done) => {
const query = gql` query people { allPeople(first: 1) { people { name } } }`;
const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
const networkInterface = mockNetworkInterface({ request: { query }, result: { data } });
const client = new ApolloClient({ networkInterface });
const withGraphQL = graphql(query);
class Container extends Component {
componentWillReceiveProps(props) {
expect(props.data.loading).to.be.false;
expect(props.data.allPeople).to.deep.equal(data.allPeople);
done();
}
render() {
return null;
}
};
const ContainerWithData = withGraphQL(Container);
mount(<ApolloProvider client={client}><ContainerWithData /></ApolloProvider>);
});
I know this is an old question, but maybe somebody gets here like I did :) These __typename fields were most likely your problem, the query ended up looking differently than the query you were passing to you mock interface. The error basically meant that it couldn't find any matching mocks for that query.
Anyway, here is a working example of this test, updated to work in Apollo 2.
it('executes a query', (done) => {
const query = gql` query people { allPeople(first: 1) { people { name } } }`;
const data = { allPeople: { people: [ { name: 'Luke Skywalker' } ] } };
const withGraphQL = graphql(query);
class Container extends React.Component {
componentWillReceiveProps(props) {
expect(props.data.loading).toBeFalsy();
expect(props.data.error).toBeUndefined();
expect(props.data.allPeople.people[0].name).toEqual(data.allPeople.people[0].name);
done();
}
render() {
return null;
}
};
const ContainerWithData = withGraphQL(Container);
mount(<MockedProvider removeTypename mocks={[ { request: { query }, result: { data } } ]}><ContainerWithData /></MockedProvider>);
});

Resources