Spying on JQuery Selectors in Jasmine
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.
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, 2020Comments
-
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 over 12 yearsI'm similarly confused.
var $foo = $('#foo')
. Now$foo
IS an existing object: it is a jQuery object. It has aval()
method; it just happens to get it by looking up its prototype chain tojQuery.fn
. So why can't I dospyOn($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. SospyOn(jQuery.fn, "hide")
doesn't give me the information I want, butspyOn($foo, "hide")
would - if it worked. -
badunk about 12 yearsThis 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 about 12 years@NathanLong is right - this is the wrong answer. I'm not sure why its been upvoted so high..
-
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 almost 11 yearsI'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 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 about 9 yearsHi , 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 over 8 yearsTry this expectation: expect(node.val).toHaveBeenCalled()
-
Venugopal almost 8 years.selector is deprecated in jQuery 1.7+. what's the alternate for that in 1.7+ versions?
-
Andrew Willems almost 8 yearsThis 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 theresult = ...
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.)