Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking jQuery to test basic use

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$;
like image 686
CrazyBS Avatar asked Feb 28 '12 19:02

CrazyBS


2 Answers

Ok, here what I came up with that does the job with minimal setup. The .extend is completely 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.

like image 81
CrazyBS Avatar answered Oct 10 '22 11:10

CrazyBS


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.

like image 1
chiccodoro Avatar answered Oct 10 '22 12:10

chiccodoro