Как проверить действие избыточного числа, которое содержит несколько запросов API и преобразований массива? - PullRequest
0 голосов
/ 22 февраля 2020

У меня есть действие redux-thunk, которое содержит несколько API-запросов, которые принимают данные, извлеченные из одной конечной точки, для извлечения других релевантных данных из другой конечной точки, а также у меня есть пара преобразований массива для объединения некоторых данных.

Хотя я не уверен, что это лучшая практика, сейчас она делает то, что мне нужно. Тем не менее, это было трудно проверить, так как я не уверен, каков правильный подход к его проверке. Я просмотрел inte rnet и посмотрел на множество различных вариантов тестов «thunk», но мой пока не дает результатов при каждом подходе.

Я буду очень признателен за некоторые советы о том, как протестировать действие типа thunk, такое как мое, или, возможно, лучшие методы реализации того, что у меня есть, если это облегчает тестирование.

My thunk-Action ...

export const fetchTopStreamsStartAsync = () => {
  return async dispatch => {
    try {
      const headers = {
        'Client-ID': process.env.CLIENT_ID
      };
      const url = 'https://api.twitch.tv/helix/streams?first=5';
      const userUrl = 'https://api.twitch.tv/helix/users?';
      let userIds = '';
      dispatch(fetchTopStreamsStart());

      const response = await axios.get(url, { headers });
      const topStreams = response.data.data;

      topStreams.forEach(stream => (userIds += `id=${stream.user_id}&`));
      userIds = userIds.slice(0, -1);

      const userResponse = await axios.get(userUrl + userIds, { headers });
      const users = userResponse.data.data;

      const completeStreams = topStreams.map(stream => {
        stream.avatar = users.find(
          user => user.id === stream.user_id
        ).profile_image_url;
        return stream;
      });

      const mappedStreams = completeStreams.map(
        ({ thumbnail_url, ...rest }) => ({
          ...rest,
          thumbnail: thumbnail_url.replace(/{width}x{height}/gi, '1280x720')
        })
      );

      dispatch(fetchTopStreamsSuccess(mappedStreams));
    } catch (error) {
      dispatch(fetchTopStreamsFail(error.message));
    }
  };
};

Один из многих тестовых подходов, которые потерпели неудачу ...

import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import axios from 'axios';
import moxios from 'moxios';

import {
  fetchTopStreamsStart,
  fetchTopStreamsSuccess,
  fetchTopStreamsStartAsync
} from './streams.actions';

const mockStore = configureMockStore([thunk]);

describe('thunks', () => {
  describe('fetchTopStreamsStartAsync', () => {
    beforeEach(() => {
      moxios.install();
    });

    afterEach(() => {
      moxios.uninstall();
    });
    it('creates both fetchTopStreamsStart and fetchTopStreamsSuccess when api call succeeds', () => {
      const responsePayload = [{ id: 1 }, { id: 2 }, { id: 3 }];

      moxios.wait(() => {
        const request = moxios.requests.mostRecent();
        request.respondWith({
          status: 200,
          response: responsePayload
        });
      });

      const store = mockStore();

      const expectedActions = [
        fetchTopStreamsStart(),
        fetchTopStreamsSuccess(responsePayload)
      ];

      return store.dispatch(fetchTopStreamsStartAsync()).then(() => {
        // return of async actions
        expect(store.getActions()).toEqual(expectedActions);
      });
    });
  });
});

Это ошибка, я попасть в проваленный тест для полученного значения ...

+     "payload": "Cannot read property 'forEach' of undefined",
    +     "type": "FETCH_TOP_STREAMS_FAIL",

ОБНОВЛЕНИЕ: Как и предположил @mgarcia, я изменил формат моего responsePayload с [{ id: 1 }, { id: 2 }, { id: 3 }] на { data: [{ id: 1 }, { id: 2 }, { id: 3 }] } и теперь я не получаю исходную ошибку, но теперь я получаю следующую ошибку:

: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error:

Что я до сих пор не понимаю, так это то, что тест должен копировать точную структуру нескольких API звонки или этого просто насмешливого ответа достаточно? Я все еще пытаюсь выяснить причину ошибки Async callback....

1 Ответ

1 голос
/ 22 февраля 2020

Вы издеваетесь над запросом ios через mox ios, но кажется, что вы не возвращаете данные в ожидаемом формате.

В вашем создателе действий вы читаете данные ответа как:

const topStreams = response.data.data;
const users = userResponse.data.data;

Но вы высмеиваете ответ так, что он возвращает:

const responsePayload = [{ id: 1 }, { id: 2 }, { id: 3 }];

Вместо этого, похоже, вы должны возвращать:

const responsePayload = { data: [{ id: 1 }, { id: 2 }, { id: 3 }] };

Помимо ложный ответ, ваш код представляет некоторые дополнительные проблемы. Во-первых, как вы сами заметили, вы только издеваетесь над первым запросом. Вы должны посмеяться над вторым запросом, а также вернуть нужные данные. Во-вторых, в вашем утверждении вы ожидаете, что действия будут созданы в:

const expectedActions = [
    fetchTopStreamsStart(),
    fetchTopStreamsSuccess(responsePayload)
];

Это не будет правдой, поскольку вы обрабатываете responsePayload в создателе действия, так что полезная нагрузка, с которой вы Вызов fetchTopStreamsSuccess в действии создателя будет отличаться от responsePayload.

С учетом всего этого ваш тестовый код может выглядеть следующим образом:

it('creates both fetchTopStreamsStart and fetchTopStreamsSuccess when api call succeeds', () => {
    const streamsResponse = [
        { user_id: 1, thumbnail_url: 'thumbnail-1-{width}x{height}' },
        { user_id: 2, thumbnail_url: 'thumbnail-2-{width}x{height}' },
        { user_id: 3, thumbnail_url: 'thumbnail-3-{width}x{height}' }
    ];
    const usersResponse = [
        { id: 1, profile_image_url: 'image-1' },
        { id: 2, profile_image_url: 'image-2' },
        { id: 3, profile_image_url: 'image-3' }
    ];
    const store = mockStore();

    // Mock the first request by URL.
    moxios.stubRequest('https://api.twitch.tv/helix/streams?first=5', {
        status: 200,
        response: { data: streamsResponse }
    });

    // Mock the second request.
    moxios.stubRequest('https://api.twitch.tv/helix/users?id=1&id=2&id=3', {
        status: 200,
        response: { data: usersResponse }
    });

    return store.dispatch(fetchTopStreamsStartAsync()).then(() => {
        expect(store.getActions()).toEqual([
            fetchTopStreamsStart(),
            {
                "type": "TOP_STREAMS_SUCCESS",
                "payload": [
                    { "avatar": "image-1", "thumbnail": "thumbnail-1-1280x720", "user_id": 1 },
                    { "avatar": "image-2", "thumbnail": "thumbnail-2-1280x720", "user_id": 2 },
                    { "avatar": "image-3", "thumbnail": "thumbnail-3-1280x720", "user_id": 3 },
                ]
            }
        ]);
    });
});

Обратите внимание, что я сделал до структуры действия fetchTopStreamsSuccess иметь атрибут type, равный TOP_STREAMS_SUCCESS, и атрибут payload с данными completeStreams. Вам, вероятно, придется приспособить это к реальной структуре действия fetchTopStreamsSuccess, которое вы создаете, чтобы пройти тест.

...