Тестирование использования подписки Apollo Hooks с реагированием - PullRequest
10 голосов
/ 29 апреля 2020

Тестирование хука useSubscription Я нахожу немного сложным, так как метод не указан / не документирован в Apollo docs (на момент написания). Предположительно, он должен быть смоделирован с использованием <MockedProvider /> из @apollo/react-testing, так же, как мутации в примерах, приведенных в этой ссылке.

Проверка состояния загрузки для работающей у меня подписки:

Компонент:

const GET_RUNNING_DATA_SUBSCRIPTION = gql`
  subscription OnLastPowerUpdate {
    onLastPowerUpdate {
      result1,
      result2,
    }
  }
`;

const Dashboard: React.FC<RouteComponentProps & Props> = props => {
  const userHasProduct = !!props.user.serialNumber;

  const [startGetRunningData] = useMutation(START_GET_RUNNING_DATA);

  const [stopGetRunningData] = useMutation(STOP_GET_RUNNING_DATA);

  useEffect(() => {
    startGetRunningData({
      variables: { serialNumber: props.user.serialNumber },
    });

    return () => {
      stopGetRunningData();
    };
  }, [startGetRunningData, stopGetRunningData, props]);

  const SubscriptionData = (): any => {
    const { data, loading } = useSubscription(GET_RUNNING_DATA_SUBSCRIPTION);

    if (loading) {
      return <Heading>Data loading...</Heading>;
    }

    const metrics = [];
    if (data) {
      console.log('DATA NEVER CALLED IN TEST!');
    }

    return metrics;
  };

  if (!userHasProduct) {
    return <Redirect to="/enter-serial" />;
  }

  return (
    <>
      <Header />
      <PageContainer size="midi">
        <Panel>
          <SubscriptionData />
        </Panel>
      </PageContainer>
    </>
  );
};

И успешный тест состояния загрузки для подписки:

import React from 'react';
import thunk from 'redux-thunk';
import { createMemoryHistory } from 'history';
import { create } from 'react-test-renderer';
import { Router } from 'react-router-dom';
import wait from 'waait';
import { MockedProvider } from '@apollo/react-testing';
import { Provider } from 'react-redux';

import configureMockStore from 'redux-mock-store';

import Dashboard from './Dashboard';

import {
  START_GET_RUNNING_DATA,
  STOP_GET_RUNNING_DATA,
  GET_RUNNING_DATA_SUBSCRIPTION,
} from './queries';

const mockStore = configureMockStore([thunk]);

const serialNumber = 'AL3286wefnnsf';

describe('Dashboard page', () => {
  let store: any;

  const fakeHistory = createMemoryHistory();

  const mocks = [
    {
      request: {
        query: START_GET_RUNNING_DATA,
        variables: {
          serialNumber,
        },
      },
      result: {
        data: {
          startFetchingRunningData: {
            startedFetch: true,
          },
        },
      },
    },
    {
      request: {
        query: GET_RUNNING_DATA_SUBSCRIPTION,
      },
      result: {
        data: {
          onLastPowerUpdate: {
            result1: 'string',
            result2: 'string'
          },
        },
      },
    },
    {
      request: {
        query: STOP_GET_RUNNING_DATA,
      },
      result: {
        data: {
          startFetchingRunningData: {
            startedFetch: false,
          },
        },
      },
    },
  ];

  afterEach(() => {
    jest.resetAllMocks();
  });

  describe('when initialising', () => {
    beforeEach(() => {
      store = mockStore({
        user: {
          serialNumber,
          token: 'some.token.yeah',
          hydrated: true,
        },
      });
      store.dispatch = jest.fn();
    });

    it('should show a loading state', async () => {
      const component = create(
        <Provider store={store}>
          <MockedProvider mocks={mocks} addTypename={false}>
            <Router history={fakeHistory}>
              <Dashboard />
            </Router>
          </MockedProvider>
        </Provider>,
      );

      expect(component.root.findAllByType(Heading)[0].props.children).toBe(
        'Data loading...',
      );
    });
  });
});

Добавление еще одного теста для ожидания, пока данные не будут разрешено из переданных макетов, согласно инструкциям в последнем примере из документов для тестирования useMutation, вы должны дождаться его.

Сломанный тест:

it('should run the data', async () => {
      const component = create(
        <Provider store={store}>
          <MockedProvider mocks={mocks} addTypename={false}>
            <Router history={fakeHistory}>
              <Dashboard />
            </Router>
          </MockedProvider>
        </Provider>,
      );
      await wait(0);
    });

Ошибка прерванных тестовых бросков:

No more mocked responses for the query: subscription OnLastPowerUpdate {

Зависимости:

    "@apollo/react-common": "^3.1.3",
    "@apollo/react-hooks": "^3.1.3",
    "@apollo/react-testing": "^3.1.3",

Вещи, которые я уже пробовал:

Github-репо с примером:

* 105 6 *https://github.com/harrylincoln/apollo-subs-testing-issue

Кто-нибудь может помочь?

1 Ответ

1 голос
/ 10 мая 2020

Проблема, которую я вижу здесь, заключается в том, что вы объявляете компонент SubscriptionData внутри компонента Dashboard, поэтому в следующий раз при повторном рендеринге компонента Dashboard будет создан другой компонент SubscriptionData, и вы см. сообщение об ошибке:

Больше не будет посмешищенных ответов на запрос: subscription OnLastPowerUpdate

Я предлагаю вынуть компонент SubscriptionData из компонента Dashboard поэтому он будет создан только один раз

const SubscriptionData = (): any => {
  const { data, loading } = useSubscription(GET_RUNNING_DATA_SUBSCRIPTION);

  if (loading) {
    return <Heading>Data loading...</Heading>;
  }

  const metrics = [];
  if (data) {
    console.log('DATA NEVER CALLED IN TEST!');
  }

  return metrics;
};

const Dashboard: React.FC<RouteComponentProps & Props> = props => {
  const userHasProduct = !!props.user.serialNumber;

  const [startGetRunningData] = useMutation(START_GET_RUNNING_DATA);

  const [stopGetRunningData] = useMutation(STOP_GET_RUNNING_DATA);

  useEffect(() => {
    startGetRunningData({
      variables: { serialNumber: props.user.serialNumber },
    });

    return () => {
      stopGetRunningData();
    };
  }, [startGetRunningData, stopGetRunningData, props]);

  if (!userHasProduct) {
    return <Redirect to="/enter-serial" />;
  }

  return (
    <>
      <Header />
      <PageContainer size="midi">
        <Panel>
          <SubscriptionData />
        </Panel>
      </PageContainer>
    </>
  );
};

А для тестов вы можете попробовать что-то вроде этого:

let component;
it('should show a loading state', async () => {
      component = create(
        <Provider store={store}>
          <MockedProvider mocks={mocks} addTypename={false}>
            <Router history={fakeHistory}>
              <Dashboard />
            </Router>
          </MockedProvider>
        </Provider>,
      );
      expect(component.root.findAllByType(Heading)[0].props.children).toBe(
        'Data loading...',
      );

      await wait(0);
});

it('should run the data', async () => {
      expect(
        // another test here
        component.root...
      ).toBe();
});
...