【问题标题】:How to test a redux-thunk action that contains multiple API requests and array transformations?如何测试包含多个 API 请求和数组转换的 redux-thunk 操作?
【发布时间】:2020-06-06 21:11:42
【问题描述】:

我有一个 redux-thunk 操作,其中包含多个 API 请求,这些请求从一个端点获取数据以从另一个端点获取其他相关数据,并且我还有几个数组转换来将一些数据合并在一起。

虽然我不确定这是否是最佳做法,但就目前而言,它可以满足我的需要。但是,很难测试,因为我不确定测试它的正确方法是什么。我搜索了互联网并查看了许多不同的“thunk”测试变体,但到目前为止,我的所有方法都失败了。

我将非常感谢一些有关如何测试诸如我的 thunk 操作的指导,或者如果它使测试更容易,也许可以更好地实施我所拥有的。

我的 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... 错误的原因。

【问题讨论】:

  • 看起来它甚至无法获取有效负载。您确定response.data.data 是您回复中数据的正确格式吗?也许在它失败的行之前抛出一个 console.log(response) 或一个断点,以确保你得到你认为你应该得到的数据。
  • 一切都按照实际应用程序中的方式运行。只有测试不起作用。我不确定......我是否必须完全按照它们在实际操作中的方式复制 API 调用?我认为架构不必与我在实际操作中收到的实际架构相匹配。

标签: reactjs redux react-redux jestjs redux-thunk


【解决方案1】:

您正在通过 moxios 模拟 axios 请求,但您似乎没有以预期的格式返回数据。

在您的动作创建器中,您将响应数据读取为:

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 操作的结构组成为具有等于TOP_STREAMS_SUCCESStype 属性,并具有带有completeStreams 数据的payload 属性。您可能必须将其适应您为通过测试而创建的 fetchTopStreamsSuccess 操作的真实结构。

【讨论】:

  • 嘿,谢谢。我这样做了,现在我没有收到最初的错误,但是现在我收到了这个错误...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:
  • 看来moxios.wait 只是在等待第一个ajax 调用,而第二个从未得到解决。我已更新我的答案以考虑到这一点。
  • 非常感谢。你很准。我试图以很多不同的方式打破它,但它完全按照预期工作,并且让我明白了很多事情。谢谢...... :)
猜你喜欢
  • 1970-01-01
  • 2019-01-24
  • 1970-01-01
  • 2018-04-16
  • 2017-03-03
  • 1970-01-01
  • 1970-01-01
  • 2020-02-04
  • 1970-01-01
相关资源
最近更新 更多