#javascript #reactjs #mocking #apollo #react-testing-library
Вопрос:
Некоторая предыстория: у меня есть компонент, который сразу же вызывает крючок useQuery при загрузке. Пока выполняется этот запрос, я вращаю загрузочный счетчик. Как только он завершится, я отрисовываю материал на основе данных.
Я добавил useEffect
крюк, который отслеживает результат запроса и регистрирует данные, вот как я заметил эту проблему.
Чтобы упростить ситуацию, это работает следующим образом:
export default function MyComponent(props: ???) {
const result = useQuery(INITIAL_DATA_QUERY, { variables: { id: 1 } });
React.useEffect(() => console.log(JSON.stringify({
loading: result.loading,
data: result.data,
error: result.error,
})), [result]);
if (result.loading) return <LoadingScreen message="Fetching data..."/>;
else if (result.error) return <ErrorPage/>
else return <Stuff info={result.data}> // omitted because it's unimportant to the issue
}
Когда я запускаю этот компонент в дикой природе, все работает точно так, как ожидалось. Он попадает в конечную точку с помощью GraphQL через Apollo, принимает решения о рендеринге на основе результата и т. Д.
Однако, когда я пытаюсь издеваться над запросом, поля result.data
и result.error
никогда не меняются, даже если это result.loading
поле меняется. Я использую react-testing-library
для запуска тестов.
Мои тесты выглядят так:
it("should load the data then render the page", () => {
const mocks = [{
request: {
query: INITIAL_DATA_QUERY,
variables: { id: 1 },
},
newData: jest.fn(() => ({
data: {
firstName: "Joe",
lastName: "Random",
}
}))
}];
const mockSpy = mocks[0].newData;
render(
<MockedProvider mocks={mocks} addTypename={false}>
<MyComponent/>
</MockedProvider>
)
// Is it a loading view
expect(result.asFragment()).toMatchSnapshot(); // Passes just fine, and matches expectations
// Wait until the mock has been called once
await waitFor(() => expect(mockSpy).toHaveBeenCalled(1)) // Also passes, meaning the mock was called
// Has the page rendered once the loading mock has finished
expect(result.asFragment()).toMatchSnapshot(); // Passes, but the page has rendered without any of the data
})
Проблема в следующем: когда я запускаю этот тест, все три из этих тестов проходят должным образом, но в конечном фрагменте данные в моем отрисованном компоненте отсутствуют. Я уверен, что макет вызывается, потому что я добавил некоторые инструкции регистратора для проверки.
Действительно сбивающая с толку часть-это значения loading
, data
, и error
, как называется макет. У меня есть useEffect
инструкция, регистрирующая их значения при изменении любого из них, и когда я запускаю тест, результат выглядит следующим образом:
{ loading: true, data: undefined, error: undefined }
{ loading: false, data: undefined, error: undefined }
Это означает, что вызывается хук и начинается загрузка, но как только загрузка заканчивается, что бы ни произошло во время загрузки, оно не вернуло никаких данных и не породило никаких ошибок.
Кто-нибудь знает, в чем может быть моя проблема здесь? Я просмотрел это с восьми сторон до воскресенья и не могу понять.
Ответ №1:
Я высмеял результат, используя result
поле.
result
поле может быть функцией, которая возвращает издевательский ответ после выполнения произвольной логики
Для меня это прекрасно работает.
MyComponent.test.tsx
:
import { gql, useQuery } from '@apollo/client';
import { useEffect } from 'react';
export const INITIAL_DATA_QUERY = gql`
query GetUser($id: ID!) {
user(id: $id) {
firstName
lastName
}
}
`;
export default function MyComponent(props) {
const result = useQuery(INITIAL_DATA_QUERY, { variables: { id: 1 } });
useEffect(
() =>
console.log(
JSON.stringify({
loading: result.loading,
data: result.data,
error: result.error,
}),
),
[result],
);
if (result.loading) return <p>Fetching data...</p>;
else if (result.error) return <p>{result.error}</p>;
else return <p>{result.data.user.firstName}</p>;
}
MyComponent.test.tsx
:
import { render, waitFor } from '@testing-library/react';
import MyComponent, { INITIAL_DATA_QUERY } from './MyComponent';
import { MockedProvider } from '@apollo/client/testing';
describe('68732957', () => {
it('should load the data then render the page', async () => {
const mocks = [
{
request: {
query: INITIAL_DATA_QUERY,
variables: { id: 1 },
},
result: jest.fn().mockReturnValue({
data: {
user: {
lastName: 'Random',
firstName: 'Joe',
},
},
}),
},
];
const mockSpy = mocks[0].resu<
const result = render(
<MockedProvider mocks={mocks} addTypename={false}>
<MyComponent />
</MockedProvider>,
);
expect(result.asFragment()).toMatchSnapshot();
await waitFor(() => expect(mockSpy).toBeCalledTimes(1));
expect(result.asFragment()).toMatchSnapshot();
});
});
результат теста:
PASS src/stackoverflow/68732957/MyComponent.test.tsx
68732957
✓ should load the data then render the page (58 ms)
console.log
{"loading":true}
at src/stackoverflow/68732957/MyComponent.tsx:18:15
console.log
{"loading":false,"data":{"user":{"firstName":"Joe","lastName":"Random"}}}
at src/stackoverflow/68732957/MyComponent.tsx:18:15
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 2 passed, 2 total
Time: 0.736 s, estimated 1 s
MyComponent.test.tsx.snap
:
// Jest Snapshot v1
exports[`68732957 should load the data then render the page 1`] = `
<DocumentFragment>
<p>
Fetching data...
</p>
</DocumentFragment>
`;
exports[`68732957 should load the data then render the page 2`] = `
<DocumentFragment>
<p>
Joe
</p>
</DocumentFragment>
`;
версии пакетов:
"@testing-library/react": "^11.1.0",
"react": "^17.0.1",
"@apollo/client": "^3.4.7",
"graphql": "^15.4.0"