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

How to unit test mapDispatchToProps with thunk action

我有以下 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" 时,dispatch 会通过 keyDown 操作调用。这是我的测试:

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(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 return相同的函数。

正如@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

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


由于 keyDown() 是从 actions 导入的,我们可以 stub return 的函数 每当使用 keyCode:

调用时,一个 dummy
import * as actions;
keyDown = stub(actions, "keyDown");
keyDown.withArgs(keyCode).returns(dummy);

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

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

为了彻底,我们应该添加单元测试以确认在目标是 <input>.

而不是 调度关键事件
mapDispatchToProps(dispatch).onKeyDown(createEvent("input", keyCode));
expect(dispatch).to.not.have.been.called;
expect(keyDown).to.not.have.been.called;

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

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

链接