I'm having a very hard time understanding how to setup an object that allows me to test my jQuery calls. I don't need to mock any Async calls or anything, just basic use. So let me set out my function that I want to test (truncated for simplicity):
listGamesCallback : function(data) {
var gameList = $("#gameList select");
gameList.empty();
$.each(data, function() {
var newOption = $('<option>', {value : this.gameId });
newOption.text(string);
newOption.data("isJoinable", isJoinable);
// Add it to the list
gameList.append(newOption);
});
}
I need to mock the jQuery here to unit test this method, but I'm unable to figure out how to do this in javascript. Even without jsMockito, I don't know how to create an object with the properties that jQuery has in this situation. Any help with this would be appreciated.
I am using jsTestDriver, jsHamcrest, jsMockito and jQuery. However a generalized approach to create a $ object that has these properties would be awesome as well. Thank you!
For those that asked, here is what I came up with that seemed to kinda work..but I don't understand why.
var saved$ = $;
var mockContruct = mockFunction();
var mockedGamelist = mock(jQuery);
var mockedOption = mock(jQuery);
mocked$ = (function() {
var test = function(name) {
var args = jQuery.makeArray(arguments);
return mockContruct.call(test, args);
};
$.extend(test, $);
// This is what confuses me. This worked, but it's wierd
// It allows me to use the regular jQuery functions like
// $.each, while returning mocked objects when selectors are used.
test.prototype.constructor = test;
return test;
})();
$ = mocked$;
when(mockContruct).call(anything(), hasItem(containsString("#gameList")))
.thenReturn(mockedGamelist);
when(mockContruct).call(anything(), hasItems(containsString("<option>"), both(object()).and(hasMember("value"))))
.thenReturn(mockedOption);
headerFunctions.listGamesCallback([ {
gameId : 1,
isWhitesTurn : false,
isGameOver : false,
whiteUserName : "foobar",
blackUserName : "barfoo"
} ]);
JsMockito.verify(mockedGamelist).empty();
JsMockito.verify(mockedGamelist).append(mockedOption);
$ = saved$;
I'm having a very hard time understanding how to setup an object that allows me to test my jQuery calls. I don't need to mock any Async calls or anything, just basic use. So let me set out my function that I want to test (truncated for simplicity):
listGamesCallback : function(data) {
var gameList = $("#gameList select");
gameList.empty();
$.each(data, function() {
var newOption = $('<option>', {value : this.gameId });
newOption.text(string);
newOption.data("isJoinable", isJoinable);
// Add it to the list
gameList.append(newOption);
});
}
I need to mock the jQuery here to unit test this method, but I'm unable to figure out how to do this in javascript. Even without jsMockito, I don't know how to create an object with the properties that jQuery has in this situation. Any help with this would be appreciated.
I am using jsTestDriver, jsHamcrest, jsMockito and jQuery. However a generalized approach to create a $ object that has these properties would be awesome as well. Thank you!
For those that asked, here is what I came up with that seemed to kinda work..but I don't understand why.
var saved$ = $;
var mockContruct = mockFunction();
var mockedGamelist = mock(jQuery);
var mockedOption = mock(jQuery);
mocked$ = (function() {
var test = function(name) {
var args = jQuery.makeArray(arguments);
return mockContruct.call(test, args);
};
$.extend(test, $);
// This is what confuses me. This worked, but it's wierd
// It allows me to use the regular jQuery functions like
// $.each, while returning mocked objects when selectors are used.
test.prototype.constructor = test;
return test;
})();
$ = mocked$;
when(mockContruct).call(anything(), hasItem(containsString("#gameList")))
.thenReturn(mockedGamelist);
when(mockContruct).call(anything(), hasItems(containsString("<option>"), both(object()).and(hasMember("value"))))
.thenReturn(mockedOption);
headerFunctions.listGamesCallback([ {
gameId : 1,
isWhitesTurn : false,
isGameOver : false,
whiteUserName : "foobar",
blackUserName : "barfoo"
} ]);
JsMockito.verify(mockedGamelist).empty();
JsMockito.verify(mockedGamelist).append(mockedOption);
$ = saved$;
Share
Improve this question
edited Mar 10, 2012 at 4:22
CrazyBS
asked Feb 28, 2012 at 19:23
CrazyBSCrazyBS
6261 gold badge6 silver badges12 bronze badges
3
- It would be useful if you provided your test code as well. :) – alpian Commented Feb 29, 2012 at 12:50
- Ok, sorry for the late reply, but I've been reassigned so this got lower priority. I've got a convoluted way to run the test that I editted into the question. It is not written very well because I don't quite understand how jQuery does what it does. – CrazyBS Commented Mar 10, 2012 at 4:03
- The test you've written doesn't really prove anything other than your code executes. It can also pollute other tests with problems if it fails because you won't restore $ if it fails. I think you're still missing the point of these tests: you should be writing a test for the behaviour you're expecting from your code, not the details of how it does it. You should try to write a test that just focuses on proving that gameList having X options with whatever values in them once your function has run. – alpian Commented Mar 11, 2012 at 22:40
4 Answers
Reset to default 1Ok, here what I came up with that does the job with minimal setup. The .extend is pletely necessary here so that the jQuery object is setup correctly. This allows you to mock the constructor to return mocked jQuery objects that you can use to run your tests on. As a spy, jQuery will work as expected in all situations except when you want it to do something else. Here it is:
TestCase("HeaderTest", {
testListGamesCallback : function () {
var saved$ = $;
$ = $.prototype.construct = jQuery.extend(spy(jQuery), jQuery);
var mockGameList = mock(jQuery);
when($)(containsString("#gameList")).thenReturn(mockGameList);
headerFunctions.listGamesCallback([ {
gameId : 1,
isWhitesTurn : false,
isGameOver : false,
whiteUserName : "foobar",
blackUserName : "barfoo"
} ]);
verify(mockGameList).empty();
verify(mockGameList).append(object());
$ = saved$;
}
});
The caveat to this solution is that mocking anything other than the constructor is a bit tricky. You will have to set each individual function that you want to mock, then program the behavior. So:
$.each = mockFunction();
when($.each)(...matchers...).thenReturn(...);
But it still allows for testing what you need to.
As an extension to alpian's answer, you can create DOM elements without having to add them to the page. Make your JS functions take the relevant elements as parameters:
listGamesCallback : function(data, gameListSelectElem) {
var gameList = $(gameListSelectElem);
...
and test them like so:
var fakeSelect = $('<select>'),
data = ...;
listGamesCallback(data, fakeSelect[0]);
equal(fakeSelect.find('option').length, 1, 'must have exactly 1 option');
...
The last line of code above is for qUnit. Take whatever you need, the point is to say you can pass a DOM element that was never added to the page and afterwards investigate that DOM element using jQuery to find whether it was manipulated right.
No sure if i understand what you mean but if you want to create 'data' for you example , this is the method i know:
var data = [ { id : 1 , name : 'foo' } , { id : 2, name : 'bar' ]
but - if you wanted to create a list of options, than you code needs a couple of fixes: see http://jsfiddle/7MMap/
var data = [ { gameId : 1 , name : 'foo' ,isJoinable:true} , { gameId : 2, name : 'bar' ,isJoinable:false}]
listGamesCallback = function(data) {
var gameList = $("#gameList select")
.empty();
$.each(data, function(i,d) {
var newOption = $('<option>', {value : d.gameId })
.text(d.name)
.data("isJoinable", d.isJoinable);
// Add it to the list
gameList.append(newOption);
})
};
listGamesCallback(data);
Mocking jQuery is not what mocking is for. You should only ever be mocking your object's collaborators. jQuery is providing you with some utilities - it's not a collaborator and hence should not be mocked.
What you are collaborating with here is the DOM, or some intermediate object between your code and the DOM. data
is a value object and can simply be created in your test as Avi suggests.
In my JS tests, i don't mock the DOM, i use the real DOM and am sure to tear down anything i created between tests and this seems to work pretty well.