Mocking jQuery $() and $ functions in jest

Viewed 2245

I have a set of tests for the functions in an ES6 class that use $.get(). I was able to mock $.get(). I'm working on testing another function in the same class that uses $.get() and $(data, ownerDocument).find(), and I haven't been able to figure out how to add mocking for the $().find() function.

How I'm mocking $.get():

const jQuery = require(path/to/jquery);
jest.mock(path/to/jquery);

describe("Description", () => {
  test("Test", () => {
    // multiple tests with different mocks of $.get, so clearing
    jQuery.get.mockClear();
    jQuery.get.mockImplementation((path) => path);
    // test function that uses $.get
  });
});

Based on this section from the jest docs, I tried adding mocking for the jQuery constructor by using the 2 argument version of jest.mock:

// jest.mock(path/to/jquery);
jest.mock(path/to/jquery, () => {
  return jest.fn().mockImplementation(arg1, arg2) => {
    return {
      find: () => console.log('find'); // just to see if structure works
    };
  });
});

describe("Description", () =>
  test("Test", () => {
    jQuery.get.mockClear();
    jQuery.get.mockImplementation((path) => path);

    // test function that uses $.get() and $(...).find()
  });
});

However, adding this broke the mocking of $.get() and I started getting this error:

TypeError: cannot read property 'mockClear' of undefined
  jQuery.get.mockClear()
             ^

Based on this article, what I believe I'm struggling with is the right way to mock functions in the $ namespace and the $.fn namespaces at the same time.

This question talks about how to mock $.ajax, and I do something like that to mock $.get but I'm not sure if it would help me mock $(...).find() as well.

Is there a way I can cleanly mock both functions I need simultaneously?

(As a side note, I'm not sure how I would create a Minimally Reproducible Example with jest, but if someone can point me to a way to do that, I'll make an MRE)

3 Answers

For future reference, I was able to resolve this by modifying the technique in the "Mock jQuery globally" portion of the accepted answer on this question. My resulting mock code looks like this:

//__mocks__/jquery.js:

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

const get = jest.fn((path) => {
    return Promise.resolve(path);
});

const when = jest.fn((prom1, prom2, prom3, prom4) => {
    return Promise.resolve();
});

const jQuery = jQ;
jQuery.get = get;
jQuery.when = when;
exports.jQuery = jQuery;

As I understand it, this is essentially a pre-ES6 version of the code provided in that answer. I'm using it as follows. I don't have to worry about mocking $().find because the mock uses the real implementation of the find function.

// foo_ut.js

const jQuery = require("../__mocks__/jquery").jQuery;

describe("Description", () =>
  test("Test", () => {
    jQuery.get.mockClear();

    // test function that uses $.get() and $(...).find()
  });
});

I use JSDom to inject the JQuery package into my window.

var jsdom = require("jsdom");
    const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
var window = dom.window;
var $ = require("jquery")(window);
expect($).to.be.not.undefined;

I also wrote an article about how i did it for vitest, should be similar to jest.

The accepted answer is fine if you are testing

  • $.method

but not

  • $(selector).method

So I have added another answer for future reference.

I want to test that $(someselector).html() has rendered

render.js

  import jquery from 'jquery';
  export default ({selector, html}) => {
    jquery(selector).html(html)
    // other stuff here...
  }

PROBLEM - using methods outside jest.mock

render.test.js


import jquery from 'jquery';

const fnHtml = jest.fn()
jest.mock('jquery', (sel) => {
  return () => ({
    html: fnHtml // This is not allowed in jest
  })
});

describe('render', () => {
  it('should invoke html method of jquery.', () => {
    const selector = '.someclass'
    const html = '<p>hello world</p>'
    const RENDERED_KEY = 'loadedHello';
    render({selector, html}) 

    expect(fnHtml).toBeCalledTimes(1)
  });
 
})

error: The module factory of jest.mock() is not allowed to reference any out-of-scope variables


SOLUTION - Using literals outside jest.mock

render.test.js

// import $ from '__tests__/__utils__/jquery';
import jquery from 'jquery';
import render from 'esfiles/js/util/render';

const arrHtml = [];

jest.mock('jquery', (sel) => {
  return () => ({
    html: (val) => {
      arrHtml.push(val) // This is ok
    }
  })
});

describe('render', () => {
  it('should do these things...', () => {
    const selector = '.someclass'
    const html = '<p>hello world</p>'
    render({selector, html})
    expect(arrHtml[arrHtml.length - 1]).toBe(html);
  });
 
})
Related