【问题标题】:How to mock jQuery .done() so it executes correctly with Jest?如何模拟 jQuery .done() 使其与 Jest 正确执行?
【发布时间】:2018-07-11 07:32:04
【问题描述】:

我正在尝试为更改密码的 React 模块编写单元测试,但我无法在括号中执行代码。我已经为模块 MyAPI 编写了一个模拟,模拟代码执行得很好,并且使用 console.log("something") 我可以在控制台中看到输出。

但是,我无法在 .done(function (data) 之后运行代码。这很可能是因为模拟正在用它自己的代码替换那些代码。

我知道一种选择是使用像 Nock 这样的假服务器,但我不想把它变成集成测试,除非我必须这样做。

我正在尝试测试的代码:

const MyAPI = require('../../my_api.js');
submitChangePasswordFormEvent(event) {
    const self = this;
    const params = {};
    event.preventDefault();
    event.stopPropagation();

    params.current_password = this.refs.current_password.getValue();
    params.passwordFirst = this.refs.passwordFirst.getValue();
    params.passwordSecond = this.refs.passwordSecond.getValue();

    MyAPI.my_api('/api/change_password/', params)
        .done(function (data) {
            // This code i would like to run but can't
            const elem = <Success>{t(['settings', 
            'passwords_changed'])}</Success>;
            self.setState({ pwerror: null, pwsuccess: elem });
            self.refs.current_password.value = '';
            self.refs.password1.value = '';
            self.refs.password2.value = '';
        })
        .error(function (errors) {
           // This code i would like to run but can't
            let msg = '';
            $.each(errors.responseJSON, function (k, v) {
                msg += v;
            });
            msg = <Error>{msg}</Error>;
            self.setState({ pwerror: msg, pwsuccess: null });
        });
}

MyAPI 的模拟文件

var MyAPI = function () {};


 MyAPI.prototype.my_api = function(url) {
 return $.ajax();
}
module.exports = new MyAPI();

还有 Jest 设置脚本:

const jqueryMock = {
ajax: function (argument) {
  return {done: function (data) {
    return {error: function (errors) {
      return "success";
    }}}}
}}

global.$ = jqueryMock;

【问题讨论】:

标签: javascript reactjs jestjs enzyme


【解决方案1】:

您希望执行 .done.error 方法,但不想实际发出请求(顺便说一句。我不知道 .error 方法只是关于 .fail)?然后我会做以下事情:

全局模拟 jQuery

​​>

在工作目录顶层的 __mocks__ 目录中为 jquery 创建一个全局模拟:

//__mocks__/jquery.js:

const jQ = jest.requireActual("jquery");

const ajax = jest.fn(() => {
    return jQ.Deferred();
});

export const $ = {
    ...jQ,  // We don't want to mock jQuery completely (we might want to alter $.Deferred status)
    ajax,
};

export default $;

通过将jquery.js 放在__mocks__ 目录中,当在您要测试的模块中请求时,jQuery 会自动被 jest 模拟(好吧,在这种情况下,它会被部分模拟...)。

使用此设置,您无需发出实际请求即可运行代码,但通常会运行 .done.error 方法和注册的回调。

模拟 .done 和 .fail 方法

如果您不想想要在.done.fail 中执行注册的回调,您需要手动模拟它们,而不是返回jQ.Deferred(),而是返回一个带有玩笑的普通javascript对象模拟。

在您绝对不希望 .done/.error 调用您注册的回调的特定测试用例中:

// By returning "this" we are able to chain in the way $.ajax("/api", params).done().fail()

const jqXHR = {
    done: jest.fn().mockImplementation(function () {
        return this;
    }),
    fail: jest.fn().mockImplementation(function () {
        return this;
    }),
    // some more $.Deferred() methods you want to mock
};

// Overwrite the global $.ajax mock implementation from __mocks__/jquery.js with our custom one
$.ajax.mockImplementation(() => jqXHR)

模拟成功或错误

当您想在特定测试用例中再次模拟成功或错误时,请覆盖全局模拟实现:

为了成功:

// success
const dfd = $.Deferred();
$.ajax.mockImplementation(() => {
    return dfd.resolve("success"); // this is what your done callback will receive as argument
});

对于错误:

// success
const dfd = $.Deferred();
$.ajax.mockImplementation(() => {
    return dfd.reject("error"); // this is what your fail callback will receive as argument
});

请注意,断言 .done.fail 被调用/未调用是没有意义的,因为它们总是被调用,因为它们注册了您放入其中的回调。只有当$.Deferred 解析或拒绝特定的注册回调时才会执行,然后您可以对其进行测试。

为了更好的可测试性 w.r.t 单元测试,您应该从 .done/.error 中剔除匿名函数。由于 JavaScript 很奇怪而且不像 python(我更喜欢),所以你不能轻易地模拟被测模块中的特定函数。因此,您需要将它们放在专用模块中并完全模拟该模块。然后你可以断言它们是在成功或错误的情况下被调用的。

我花了一段时间才弄清楚如何正确处理使用 jquery 进行模拟,所以我想在这里分享我的经验。希望这会有所帮助...

【讨论】:

    猜你喜欢
    • 2019-06-10
    • 1970-01-01
    • 2017-02-09
    • 1970-01-01
    • 2019-10-04
    • 1970-01-01
    • 1970-01-01
    • 2019-10-03
    • 1970-01-01
    相关资源
    最近更新 更多