Spying on JQuery Selectors in Jasmine

47,881

Solution 1

This line is wrong:

spyOn($("#Something"), 'val').andReturn("bar");

Jasmine's spyOn function expects two parameters. The first is an existing object. The second is a function name as a string. You are correctly passing in the function name as a string ("val") but you are not passing in an existing object as the first parameter.

$("#Something")

...is not an existing object. It is the result (the return value) of a jQuery selector. More specifically, it will return a jQuery object representing the matched nodes - kind of like an array of results.

$

...is an existing object.

$.fn

...is an existing object.

$("#Something")

...is not an existing object - it is the result of a jQuery selector.

This will work:

it("should be able to mock DOM call", function () {
    //spyOn($.fn, "val").andReturn("bar"); //pre-jasmine 2.0 syntax
    spyOn($.fn, "val").and.returnValue("bar"); //Jasmine 2.0 Syntax
    var result = $("#Something").val();
    expect(result).toEqual("bar");
});

Solution 2

Seems like I found good solution

    it "should open past statuses", ->
      # We can't use $('.past') here cause each time $('.past') called it returns different objects
      # so we need to store spy in variable
      showSpy = spyOn($.fn, 'show')
      # do the stuff
      $('.show-past').click()
      # then check if 'show' action was called
      expect($.fn.show).toHaveBeenCalled()
      # and if it realy our object
      expect(showSpy.mostRecentCall.object.selector).toEqual('.past')

This is not based on your code but i hope this can help someone. And, yes, example in CoffeScript.

Solution 3

The problem is that the two calls to $ return two different jQuery-wrapped nodes.

This should work:

it("should be able to mock DOM call", function(){

  // var node = $("Something");
  // spyOn(node, 'val').andReturn('bar');

  // expect(node.val()).toEqual('bar');
  var node = $("Something");
  spyOn(node, 'val').and.returnValue('bar');

  expect(node.val()).toEqual('bar');
});

Next time, help is more prevalent on the Jasmine mailing list: [email protected].

Solution 4

You could create your own fake DOM element and then use $('#elementid')[0] as usual

addFakeElementWithId = function (elementId) {
      var fake = document.createElement("div");
      fake.setAttribute("id", elementId);
      document.body.appendChild(fake);
   };

Solution 5

I wrote a helper-function, which accepts an array of id/value-pairs.

var jasminTestHelper = {
    spyOnValAndFake : function(obj) {
        var i, j;
        spyOn($.fn, 'val').andCallFake(function() {
            for ( i = 0, j = obj.length; i < j; i++) {
                if (this.selector === '#' + obj[i][0]) {
                    return obj[i][1];
                }
            }
        })
    }
}

Each pair tells the faker-function for which id, which value should be returned if the jQuery-val()-function is called with the id-selector. It is used like this:

jasminTestHelper.spyOnValAndFake([["id1", "value1"], ["id2", "value2"]]);

If $('#id1').val() is called in your function under test, the fake-function returns value1, if $('#id2').val() is called it returns value2. So you don't need to fiddle with the DOM, you just mock the jQuery-val()-function and simulate return-values. Other jQuery-functions could probably mocked the same way.

Share:
47,881
Daniel Elliott
Author by

Daniel Elliott

Never enough sleep, often too much Visual Studio, always happy to help and rarely far away from the computer! Linked In

Updated on February 01, 2020

Comments

  • Daniel Elliott
    Daniel Elliott over 4 years

    I am unit testing some JavaScript with Jasmine and wish to spy on (mock) an element of the DOM that is accessed by a jQuery selector.

    My spec is:

    it("should be able to mock DOM call", function() {
    
        spyOn($("#Something"), 'val').andReturn("bar");
    
        result = $("#Something").val();
    
        expect(result).toEqual("bar");
    
    });
    

    In my specrunner.html I have:

    <input type="hidden" id="Something" value="foo" />
    

    Unfortunately the spec fails with:

    should be able to mock DOM call Expected 'foo' to equal 'bar'.

  • Nathan Long
    Nathan Long over 12 years
    I'm similarly confused. var $foo = $('#foo'). Now $foo IS an existing object: it is a jQuery object. It has a val() method; it just happens to get it by looking up its prototype chain to jQuery.fn. So why can't I do spyOn($foo, "val")? Why does Jasmine's spy require me to specify where the method is defined? My use case is that I want to check that, say, hide() has been called, not just in general, but on $foo. So spyOn(jQuery.fn, "hide") doesn't give me the information I want, but spyOn($foo, "hide") would - if it worked.
  • badunk
    badunk about 12 years
    This is your answer, jQuery returns a different object for each query, even if its the same selector. You need to reference the same object in order to properly spy on it. Otherwise, you're spying on one, and executing on another.
  • badunk
    badunk about 12 years
    @NathanLong is right - this is the wrong answer. I'm not sure why its been upvoted so high..
  • Leonardo Garcia Crespo
    Leonardo Garcia Crespo almost 12 years
    @NathanLong The problem is that every time you execute a jquery selector, you get a brand new object, so the one you set up to spy upon is going to be different than the one you get inside the code under test. Hence Alex is telling you to spy the prototype ($.fn) for all the jquery objects.
  • alxndr
    alxndr almost 11 years
    I've been barking up this tree for $(selector) calls. At the moment I'm wrestling with making a helper for mocking multiple selectors at once, but for now what I've got is posted up at github.com/alxndr/sundries/blob/master/spy-on-jQuery-helper.‌​js
  • Greg
    Greg over 10 years
    @badunk it's upvoted so high because it answers the question: "what is actually called when I use jQuery('foo')" ;)
  • N.K
    N.K about 9 years
    Hi , Can you please help me on the BLOCK method of jQuery ,I am not able to check its called count in Jasmine/Sinon test case ...
  • Gus Crawford
    Gus Crawford over 8 years
    Try this expectation: expect(node.val).toHaveBeenCalled()
  • Venugopal
    Venugopal almost 8 years
    .selector is deprecated in jQuery 1.7+. what's the alternate for that in 1.7+ versions?
  • Andrew Willems
    Andrew Willems almost 8 years
    This may work but can lead to problems because the spy will be used when the val() method is invoked on any jQuery object. E.g. the test will still (inappropriately) pass even if the id is accidentally changed either in the test in the result = ... line or, worse, in the <input> element in the html. For this reason, it seems to me that the answer by @user588542 is a better one. (I'm still voting this up, because it does solve the problem posed, and in an intriguing way, even if it's not the best solution.)