gpt4 book ai didi

javascript - 如何测试仅调度其他 Action 的Redux Action 创建者

转载 作者:行者123 更新时间:2023-12-03 13:40:41 26 4
gpt4 key购买 nike

我在测试一个 Action 创建者时遇到麻烦,该 Action 创建者只是循环遍历传递给它的数组,并为该数组中的每个项目分配一个 Action 。这很简单,我似乎无法弄清楚。这是 Action 创建者:

export const fetchAllItems = (topicIds)=>{
return (dispatch)=>{
topicIds.forEach((topicId)=>{
dispatch(fetchItems(topicId));
});
};
};

这是我尝试测试的方式:
describe('fetchAllItems', ()=>{
it('should dispatch fetchItems actions for each topic id passed to it', ()=>{
const store = mockStore({});
return store.dispatch(fetchAllItems(['1']))
.then(()=>{
const actions = store.getActions();
console.log(actions);
//expect... I can figure this out once `actions` returns...
});
});
});

我收到此错误: TypeError: Cannot read property 'then' of undefined

最佳答案

编写和测试Redux Thunk Action Creator的指南,这些创建者向API提出基于 promise 的请求

前言

本示例使用Axios,这是一个基于Promise的库,用于发出HTTP请求。但是,您可以使用其他基于 promise 的请求库(例如Fetch)运行此示例。或者,只需将一个普通的http请求包装在promise中。

本示例中将使用Mocha和Chai进行测试。

用Redux操作表示请求的状态

从redux文档:

When you call an asynchronous API, there are two crucial moments in time: the moment you start the call, and the moment when you receive an answer (or a timeout).



我们首先需要定义 Action 和它们的创建者,这些 Action 和它们的创建者针对任何给定的主题ID进行对外部资源的异步调用。

promise 的三种可能状态代表一个API请求:
  • 待定(已提出要求)
  • 已完成(请求成功)
  • 拒绝(请求失败-或超时)

  • 表示请求状态的核心 Action 创建者 promise

    好吧,让我们编写核心 Action 的创建者,我们将需要表示给定主题ID的请求的状态。
    const fetchPending = (topicId) => {
    return { type: 'FETCH_PENDING', topicId }
    }

    const fetchFulfilled = (topicId, response) => {
    return { type: 'FETCH_FULFILLED', topicId, response }
    }

    const fetchRejected = (topicId, err) => {
    return { type: 'FETCH_REJECTED', topicId, err }
    }

    请注意, reducer 应适当处理这些操作。

    单个提取操作创建者的逻辑

    Axios是基于 promise 的请求库。因此axios.get方法向给定的url发出请求,并返回一个 promise ,如果成功,该 promise 将被解决,否则该 promise 将被拒绝
     const makeAPromiseAndHandleResponse = (topicId, url, dispatch) => {
    return axios.get(url)
    .then(response => {
    dispatch(fetchFulfilled(topicId, response))
    })
    .catch(err => {
    dispatch(fetchRejected(topicId, err))
    })
    }

    如果我们的Axios请求是 成功,则我们的 promise 将得到解决,并且 中的代码将被执行。这将为给定的主题ID调度FETCH_FULFILLED操作,并带有请求的响应(我们的主题数据)

    如果Axios请求为 不成功,则我们在 中的代码将被执行并调度FETCH_REJECTED操作,该操作将包含主题ID和请求期间发生的错误。

    现在,我们需要创建一个 Action 创建者,以开始获取多个topicId的过程。

    由于这是一个异步过程,因此我们可以使用thunk Action 创建者 (它将使用Redux的thunk中间件)来允许我们将来调度其他异步 Action 。

    Thunk Action创建者如何工作?

    我们的重击 Action 创建者会调度与为多个 topicIds进行提取相关的 Action 。

    这个单一的thunk Action 创建者是一个 Action 创建者,将由我们的redux thunk中间件处理,因为它适合与thunk Action 创建者相关联的签名,即它返回一个函数。

    调用store.dispatch时,我们的操作将在到达商店之前经过中间件链。 Redux Thunk是一块中间件,它将看到我们的操作是一个函数,然后使该函数访问商店的调度和获取状态。

    以下是Redux thunk内部的代码:
    if (typeof action === 'function') {
    return action(dispatch, getState, extraArgument);
    }

    好的,这就是为什么我们的重击 Action 创建者返回一个函数的原因。因为此函数将由中间件调用,并允许我们访问调度和获取状态,因此我们可以在以后调度进一步的操作。

    编写我们的重击 Action 创建者
    export const fetchAllItems = (topicIds, baseUrl) => {
    return dispatch => {

    const itemPromisesArray = topicIds.map(id => fetchItem(dispatch, id, baseUrl))

    return Promise.all(itemPromisesArray)
    };
    };

    最后,我们回电给promise.all。

    ,这意味着我们的重击 Action 创建者返回一个 promise ,它等待我们的所有子 promise ,这些子 promise 代表要完成的单个提取(请求成功)或第一次拒绝(请求失败)

    看到它返回接受分派(dispatch)的函数。这个返回的函数是将在Redux thunk中间件内部调用的函数,因此,在对外部资源进行获取后,将控制权反转并让我们调度更多操作。

    旁听-在我们的重 Action 创建者
    中访问getState

    正如我们在上一个函数中看到的那样,redux-thunk使用dispatch和getState调用了 Action 创建者返回的函数。

    我们可以将其定义为由thunk Action 创建者返回的函数内部的arg,如下所示
    export const fetchAllItems = (topicIds, baseUrl) => {
    return (dispatch, getState) => {

    /* Do something with getState */
    const itemPromisesArray = topicIds.map(id => fetchItem(dispatch, id, baseUrl))

    return Promise.all(itemPromisesArray)
    };
    };

    请记住,redux-thunk不是唯一的解决方案。如果我们想分派(dispatch)promise而不是函数,则可以使用redux-promise。但是,我建议从redux-thunk开始,因为这是最简单的解决方案。

    测试我们的重击 Action 创建者

    因此,针对我们的重击 Action 创建者的测试将包括以下步骤:
  • 创建一个模拟存储。
  • 调度重击 Action 创建者
    3.确保在数组中传递给thunk操作创建者的每个主题ID的所有异步提取完成之后,已经调度了FETCH_PENDING操作。

  • 但是,我们需要执行另外两个子步骤以创建此测试:
  • 我们需要模拟HTTP响应,这样我们就不会真正向实时服务器发出请求
  • 我们还想创建一个模拟存储,使我们能够查看所有已调度的历史 Action 。

  • 拦截HTTP请求

    我们要测试通过一次调用fetchAllItems Action 创建者来调度某个 Action 的正确数量。

    好的,现在在测试中,我们实际上不想对给定的api发出请求。请记住,我们的单元测试必须快速且确定。对于给重击操作创建者的一组给定参数,我们的测试必须总是失败或通过。如果我们实际上是在测试中从服务器中获取数据,则该数据可能会通过一次,然后在服务器宕机时失败。

    模拟服务器响应的两种可能方法
  • 模拟Axios.get函数,以便它返回一个 promise ,我们可以强制使用所需的数据进行解析,或者使用预定义的错误拒绝该数据。
  • 使用类似Nock的HTTP模拟库,它将使Axios库发出请求。但是,此HTTP请求将由Nock而不是真正的服务器拦截和处理。通过使用Nock,我们可以在测试中指定给定请求的响应。

  • 我们的测试将从以下内容开始:
    describe('fetchAllItems', () => {
    it('should dispatch fetchItems actions for each topic id passed to it', () => {
    const mockedUrl = "http://www.example.com";
    nock(mockedUrl)
    // ensure all urls starting with mocked url are intercepted
    .filteringPath(function(path) {
    return '/';
    })
    .get("/")
    .reply(200, 'success!');

    });

    Nock拦截对以 http://www.example.com开头的URL的任何HTTP请求
    并以确定的方式响应状态码和响应。

    创建我们的Mock Redux商店

    在测试文件中,从redux-mock-store库中导入configure store功能以创建我们的假存储。
    import configureStore from 'redux-mock-store';

    该模拟存储将在数组中调度的 Action 将在测试中使用。

    由于我们正在测试一个thunk Action 创建者,因此我们的模拟存储需要在测试中配置为redux-thunk中间件
    const middlewares = [ReduxThunk];
    const mockStore = configureStore(middlewares);

    模拟商店中有一个store.getActions方法,该方法在调用时为我们提供了所有先前调度的 Action 的数组。

    最后,我们分派(dispatch)thunk Action 创建者,该创建者返回一个promise,当所有单独的topicId提取promise都解决时,promise就会解决。

    然后,我们进行测试声明,以将要分派(dispatch)到模拟存储的实际操作与我们的预期操作进行比较。

    在Mocha中测试重击 Action 创建者返回的 promise

    因此,在测试结束时,我们将重制 Action 创建者分配到了模拟商店。我们一定不要忘记返回此调度调用,以便在解决重击操作创建者返回的 promise 时,断言将在.then块中运行。
      return store.dispatch(fetchAllItems(fakeTopicIds, mockedUrl))
    .then(() => {
    const actionsLog = store.getActions();
    expect(getPendingActionCount(actionsLog))
    .to.equal(fakeTopicIds.length);
    });

    请参阅下面的最终测试文件:

    最终测试文件

    测试/ index.js
    import configureStore from 'redux-mock-store';
    import nock from 'nock';
    import axios from 'axios';
    import ReduxThunk from 'redux-thunk'
    import { expect } from 'chai';

    // replace this import
    import { fetchAllItems } from '../src/index.js';


    describe('fetchAllItems', () => {
    it('should dispatch fetchItems actions for each topic id passed to it', () => {
    const mockedUrl = "http://www.example.com";
    nock(mockedUrl)
    .filteringPath(function(path) {
    return '/';
    })
    .get("/")
    .reply(200, 'success!');

    const middlewares = [ReduxThunk];
    const mockStore = configureStore(middlewares);
    const store = mockStore({});
    const fakeTopicIds = ['1', '2', '3'];
    const getPendingActionCount = (actions) => actions.filter(e => e.type === 'FETCH_PENDING').length

    return store.dispatch(fetchAllItems(fakeTopicIds, mockedUrl))
    .then(() => {
    const actionsLog = store.getActions();
    expect(getPendingActionCount(actionsLog)).to.equal(fakeTopicIds.length);
    });
    });
    });

    最终操作的创建者和帮助函数

    src / index.js
    // action creators
    const fetchPending = (topicId) => {
    return { type: 'FETCH_PENDING', topicId }
    }

    const fetchFulfilled = (topicId, response) => {
    return { type: 'FETCH_FULFILLED', topicId, response }
    }

    const fetchRejected = (topicId, err) => {
    return { type: 'FETCH_REJECTED', topicId, err }
    }

    const makeAPromiseAndHandleResponse = (topicId, url, dispatch) => {
    return axios.get(url)
    .then(response => {
    dispatch(fetchFulfilled(topicId, response))
    })
    .catch(err => {
    dispatch(fetchRejected(topicId, err))
    })
    }

    // fundamentally must return a promise
    const fetchItem = (dispatch, topicId, baseUrl) => {
    const url = baseUrl + '/' + topicId // change this to map your topicId to url
    dispatch(fetchPending(topicId))
    return makeAPromiseAndHandleResponse(topicId, url, dispatch);
    }

    export const fetchAllItems = (topicIds, baseUrl) => {
    return dispatch => {
    const itemPromisesArray = topicIds.map(id => fetchItem(dispatch, id, baseUrl))
    return Promise.all(itemPromisesArray) // return a promise that waits for all fulfillments or first rejection
    };
    };

    关于javascript - 如何测试仅调度其他 Action 的Redux Action 创建者,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41525794/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com