最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to mock jQuery .done() so it executes correctly with Jest? - Stack Overflow

programmeradmin4浏览0评论

I'm trying to write a unit-test for a password changing React-module, but i can't get the code to be executed in brackets. I've written a mock for the module MyAPI, the mock code is executed just fine and with console.log("something") i can see the output in console.

However, i can't get the code to run after the .done(function (data). It's most likely because the mock is replacing those with it's own code.

I know one option is to use a fake server like Nock, but I wouldn't want to turn it into an integration test unless i have to.

Code that i'm trying to test:

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 });
        });
}

Mock-file for MyAPI

var MyAPI = function () {};


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

And the Jest set-up script:

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

global.$ = jqueryMock;

I'm trying to write a unit-test for a password changing React-module, but i can't get the code to be executed in brackets. I've written a mock for the module MyAPI, the mock code is executed just fine and with console.log("something") i can see the output in console.

However, i can't get the code to run after the .done(function (data). It's most likely because the mock is replacing those with it's own code.

I know one option is to use a fake server like Nock, but I wouldn't want to turn it into an integration test unless i have to.

Code that i'm trying to test:

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 });
        });
}

Mock-file for MyAPI

var MyAPI = function () {};


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

And the Jest set-up script:

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

global.$ = jqueryMock;
Share Improve this question asked Jul 11, 2018 at 7:32 Ilmari KumpulaIlmari Kumpula 1,5431 gold badge15 silver badges18 bronze badges 1
  • Does this answer your question? mocking jquery $.ajax with jest – Michael Freidgeim Commented May 28, 2020 at 23:36
Add a ment  | 

1 Answer 1

Reset to default 7

You want that .done or .error methods are executed but don't want to actually make a request (btw. i don't know about an .error method just about .fail) ? Then i would do the following:

Mock jQuery globally

Create a global mock for jquery inside a __mocks__ directory at the top level of your working directory:

//__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 pletely (we might want to alter $.Deferred status)
    ajax,
};

export default $;

By putting jquery.js inside the __mocks__ directory jQuery gets automatically mocked by jest when requested in modules you want to test (well, in this case it gets partially mocked...).

With this setup you can just run your code without making an actual request but normally run .done and .error methods and the registered callbacks.

Mock .done and .fail methods

If you don't want to execute the registered callbacks in .done or .fail you need to mock them by hand and instead of returning jQ.Deferred() return a plain javascript object with jest mocks.

Inside a specific test case where you definitly don't want that .done/.error calls your registered callback:

// 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)

Simulate success or error

When you want to simulate success or error inside a specific test case again overwrite the global mock implementation:

For success:

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

For error:

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

Note that it does not make sense to assert that .done or .fail was called/not called since both are always called because they register the callbacks you put inside them. Only when $.Deferred resolves or rejects a specific registered callback gets executed, which you then can test.

For better testability w.r.t unit testing you should factor out the anonymous functions from .done/.error. Since JavaScript is weird and not like python (which i like more) you cannot easily mock specific functions inside a module under test. So you would need to put them inside a dedicated module and mock this module pletely. Then you could just assert that they were called either in success or error case.

It took me a while to figure out how to correctly handle mocking with jquery so i want to share my experience here. Hope this helps...

发布评论

评论列表(0)

  1. 暂无评论