如何使用 thunk 操作对 mapDispatchToProps 进行单元测试

Posted

技术标签:

【中文标题】如何使用 thunk 操作对 mapDispatchToProps 进行单元测试【英文标题】:How to unit test mapDispatchToProps with thunk action 【发布时间】:2017-05-28 17:57:03 【问题描述】:

我有以下 Redux 动作创建者:

export const keyDown = key => (dispatch, getState) => 
    const  modifier  = getState().data;
    dispatch( type: KEYDOWN, key );
    return handle(modifier, key); // Returns true or false
;

还有以下连通分量:

export const mapDispatchToProps = dispatch => (
    onKeyDown: e => 
        if(e.target.tagName === "INPUT") return;
        const handledKey = dispatch(keyDown(e.keyCode));
        if(handledKey) 
            e.preventDefault();
        
    
);

我正在尝试编写一个测试,以确保当tagName 不是"INPUT" 时,使用keyDown 操作调用dispatch。这是我的测试:

import  spy  from "sinon";
import keycode from "keycodes";
import  mapDispatchToProps  from "./connected-component";
import  keyDown  from "./actions";

// Creates a simple Event stub...
const createEvent = (tag, keyCode) => (
    target: 
        tagName: tag.toUpperCase()
    ,
    preventDefault: spy(),
    keyCode
);

it("Dispatches a keyDown event with the specified keyCode if the selected element is not an <input>", () => 
    const dispatch = spy();
    const keyCode = keycode("u");
    mapDispatchToProps(dispatch).onKeyDown(createEvent("div", keyCode));
    // This fails...
    expect(dispatch).to.have.been.calledWith(keyDown(keycode));
);

大概这与使用箭头函数有关?有什么方法可以确保使用我期望的函数签名调用调度?

【问题讨论】:

那么您是在测试字符串相等性和返回语句是否有效,还是开发人员没有意外删除它?上帝我不喜欢大多数单元测试:( 主要是实际调用了dispatch。很多时候,我调用动作创建者而不传递调度。检查 keyDown 动作是否通过也很重要,所以我认为 expect(dispatch).to.have.been.called 还不够 【参考方案1】:

keyDown(keycode)每次都会创建一个新函数,而且每个函数实例都不一样,测试用例按预期失败。

这可以通过记忆keyDown创建的函数来解决:

let cacheKeyDown = ;
export const keyDown = key => cacheKeyDown[key] || cacheKeyDown[key] = (dispatch, getState) => 
    const  modifier  = getState().data;
    dispatch( type: KEYDOWN, key );
    return handle(modifier, key);
;

通过记忆,keyDown 调用相同的keycode 返回相同的函数。

【讨论】:

【参考方案2】:

正如@DarkKnight 所说(获得+1),keyDown 为每次调用返回一个新函数,因此测试失败,因为keyDown(keyCode) != keyDown(keyCode)

如果您不想更改keyDown 的实际实现,您可以在测试中模拟:

import * as actions from "./actions";   

spyOn(actions, 'keyDown');  

您可以查看有关如何完成此操作的其他答案:

How to mock the imports of an ES6 module?

How to mock dependencies for unit tests with ES6 Modules

【讨论】:

【参考方案3】:

最简单的解决方案可能是记忆 keyDown(),正如另一个答案 (+1) 中所建议的那样。这是一种尝试涵盖所有基础的不同方法...


由于keyDown()是从actions导入的,我们可以stub 返回的函数 一个 dummy 值,每当它被 keyCode 调用时:

import * as actions;
keyDown = stub(actions, "keyDown");
keyDown.withArgs(keyCode).returns(dummy);

然后,我们的 unit tests 将验证 dispatch 是使用我们之前设置的 dummy 调用的。我们知道 dummy 只能由我们的存根 keyDown() 返回,所以这个检查也验证了 keyDown() 被调用了。

mapDispatchToProps(dispatch).onKeyDown(createEvent("div", keyCode));
expect(dispatch).to.have.been.calledWith(dummy);
expect(keyDown).to.have.been.calledWithExactly(keyCode);

为了彻底,我们应该添加单元测试以确认当目标是&lt;input&gt; 时,关键事件没有分派。

mapDispatchToProps(dispatch).onKeyDown(createEvent("input", keyCode));
expect(dispatch).to.not.have.been.called;
expect(keyDown).to.not.have.been.called;

我们还应该通过验证给定的 dispatch 回调是使用正确的键事件和键调用来隔离test keyDown() 本身 代码:

expect(dispatch).to.have.been.calledWith(type: actions.KEYDOWN, key: keyCode);

链接

GitHub demo 100% test coverage

【讨论】:

以上是关于如何使用 thunk 操作对 mapDispatchToProps 进行单元测试的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Redux connect 中的操作对测试组件进行快照?

如何使我的代码诊断语法节点操作对关闭的文件起作用?

使用C++的函数memset()时要注意它的操作对象是每一个字节

RDD操作对pyspark中的值进行排序

url_for()中的坑,url_for操作对象是函数,而不是route里的路径

应用man九章和一些基本操作对linux的初步了解