How to mock jQuery with Jasmine
Asked Answered
F

2

6

What I want to do: We are writing some tests for an existing Javascript code base that uses jQuery heavily. For the tests, we don't want to have actual HTML elements (HTML fixtures). We'd prefer it if we had a jQuery mock object that does not do anything HTML-related.

My starting point: The most promising approach I found here:

http://eclipsesource.com/blogs/2014/03/27/mocks-in-jasmine-tests/

This creates a helper method that creates a mock by going through the functions of an object and creating a spy for each function:

window.mock = function (constr, name) {
  var keys = [];
  for (var key in constr.prototype)
    keys.push( key );
  return keys.length > 0 ? jasmine.createSpyObj( name || "mock", keys ) : {};
};

Then, if I understand him correctly, he uses that like this (adapted example from his blog post):

var el = mock($);
el('.some-not-existing-class').css('background', 'red');
expect(el.css).toHaveBeenCalledWith('background', 'red');

However, this does not work, since el is an object and not a function.

My approach to resolve this problem: I refactored his mock function to account for the case that constr is a function:

mock (constr, name) {
  var keys = [];
  for (var key in constr.prototype)
    keys.push(key);
  var result = keys.length > 0 ? jasmine.createSpyObj(name || "mock", keys) : {};

  // make sure that if constr is a function (like in the case of jQuery), the mock is too
  if (typeof constr === 'function') {
    var result2 = result;
    result = jasmine.createSpy(name || 'mock-fn');
    for (var key in result2)
      result[key] = result2[key];
  }
  return result;
}

However, the second line in the test throws a Cannot read property css of undefined error:

var el = mock($);
el('.some-not-existing-class').css('background', 'red');
expect(el.css).toHaveBeenCalledWith('background', 'red');

Other ideas: I also tried to merge the spy object into jQuery, but that did not help either.

Any ideas? I hope we're not the only ones who do it without HTML fixtures.

Fingerprint answered 21/6, 2015 at 11:28 Comment(0)
F
0

Found it. When my version of the mock function is set up to return the jQuery object when the jQuery function is invoked, the test works:

mock (constr, name) {
  var keys = [];
  for (var key in constr.prototype)
    keys.push(key);
  var result = keys.length > 0 ? jasmine.createSpyObj(name || "mock", keys) : {};

  // make sure that if constr is a function (like in the case of jQuery), the mock is too
  if (typeof constr === 'function') {
    var result2 = result;
    result = jasmine.createSpy(name || 'mock-fn');
    result.and.returnValue(result);
    for (var key in result2)
      result[key] = result2[key];
  }
  return result;
}
Fingerprint answered 21/6, 2015 at 11:40 Comment(0)
W
0

You may use sinon.js stubs, instead of rolling your own helper methods.

stub = sinon.stub(jQuery.fn, 'css');

// invoke code which calls the stubbed function

expect(stub.calledWith({ 'background': 'red' })).toBe(true);
stub.restore();
Whitechapel answered 21/6, 2015 at 11:39 Comment(1)
Tried that with the line $('abc').css('background', 'red'); as test code. However, the assert fails, sub.calledWith returns false.Fingerprint
F
0

Found it. When my version of the mock function is set up to return the jQuery object when the jQuery function is invoked, the test works:

mock (constr, name) {
  var keys = [];
  for (var key in constr.prototype)
    keys.push(key);
  var result = keys.length > 0 ? jasmine.createSpyObj(name || "mock", keys) : {};

  // make sure that if constr is a function (like in the case of jQuery), the mock is too
  if (typeof constr === 'function') {
    var result2 = result;
    result = jasmine.createSpy(name || 'mock-fn');
    result.and.returnValue(result);
    for (var key in result2)
      result[key] = result2[key];
  }
  return result;
}
Fingerprint answered 21/6, 2015 at 11:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.