React component using jQuery without require - jest unit tests

11.7k views Asked by At

I have a very simple React mixin which uses jQuery to trigger an event

MyMixin = {
  trackStructEvent: function () {
    args = Array.prototype.slice.call(arguments);
    $('body').trigger('myEvent', args);
  }
module.exports = MyMixin

This is imported into the main site as part of a new set of components using browserify. As the main site holding these components will always include jQuery, I don't want to require jQuery with browserify, as it will be duplicated.

This isn't an issue in terms of behaviour - however it causes problems when running jest to unit test the components using this mixin throwing the error.

ReferenceError: $ is not defined

I know I can fix this by including jQuery through browserify, but that will load 2 copies into my site.

Is there any way in jest to tell my react component that jQuery already exists on the window and not to worry about it?

3

There are 3 answers

1
brommersman On BEST ANSWER

I have a similar problem but I think where i've got to so far solves your problem. You need to use require inside your react files to define jQuery under $

var $ = require('jquery');

MyMixin = {
   trackStructEvent: function () {
      args = Array.prototype.slice.call(arguments);
      $('body').trigger('myEvent', args);
   }
module.exports = MyMixin

You then need to tell jest not to mock jquery

jest.dontMock('jquery')

Then your jest unit tests should pass (assuming they aren't verifying things that jQuery is doing - this is where my tests are falling over).

You also need to tell browserify that jQuery is external then the result will be that all your references to $ have been defined, the jquery library is loaded for testing but is not included in your browserify bundle. (use browserify-shim)

0
Don On

For my part, I am not interested in testing the jQuery functionality in my components. In addition, I have not included jQuery in my bundle because it is used and included all over the website my app is on. So I have, for example, done the following:

// SubmitRender.js

// ...import statements...

var SubmitRender = React.createClass({
  componentWillMount: function () {
    $('form').on('submit', (e)=>{
      this.handleSubmit(e);
    });
  },

  // ...more code...
});
export default SubmitRender;

.

// SubmitRender.spec.js

// ...import statements...

describe('SubmitRender', () => {
  beforeAll(() => {
    // mocking jQuery $()
    global.window.$ = jest.fn(() => {return {on: jest.fn()}});
  });

  it('renders without error', () => {
    expect( () => render(<SubmitRender />) ).not.toThrow();
  });
});

I know my component will not mount without calling the $ function. So I just mock out that function with jest.fn in a beforeAll(). Now, I also know that my jQuery function inside my actual component is chained. So I know that I also need to account for jQuery's .on(), so I make sure to return an object with an on function. There is no further chaining, so that is where I can stop.

If I had a more complex set of chained functions, I could do something like this:

beforeAll(() => {
  // mocking jQuery $()
  var fakeQuery = jest.fn(() => {
    return {
      on: fakeQuery,
      off: fakeQuery,
      click: fakeQuery
    }
  })
  global.window.$ = fakeQuery;
});
0
Zachary Ryan Smith On

What I'm going with is:

if (process.env.NODE_ENV === 'test') window.$ = require('jquery');

I hope this helps someone!

UPDATE:

I've begun to import $ from 'jquery' in all React files that use jQuery. This removes the dependency on window.$ being the type of jQuery I expect (I have run into problems with other libraries messing with window.$. At first, I was concerned about importing jQuery everywhere, because I was afraid that this would increase my bundle size. But the way bundling works, all these imports will point to a singleton, so bundle size would not increase.