Apollo MockedProvider всегда возвращает неопределенное поле данных, даже после загрузки

#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"