10

Trying to test that an event handler gets called on a clicked element with Jasmine. Have a "Pad" object that contains a DOM element "PadElement", which gets clicked. The event handler is a method on the Pad object:

GRAPH.Pad = function(graphDiv, graph) {
    this.graph = graph;

    this.clickHandler = function(e) {
        console.log('padElement clickHandler called');
        //this.graph.createVertex(e.clientX, e.clientY);
    };
    this.padElement = GRAPH.padElement(graphDiv, this.clickHandler);
}

GRAPH.padElement = function(graphDiv, clickHandler) {
    //Initialize pad
    var NS="http://www.w3.org/2000/svg";
    var pad=document.createElementNS(NS,"svg");
    pad.setAttributeNS(null, 'id', 'pad');
    graphDiv.appendChild(pad);
    pad.addEventListener('click', clickHandler)
    return pad;
}

The Jasmine test:

var testDiv = document.createElement('div');
var testGraph = new GRAPH.Graph(testDiv);
var testPad = new GRAPH.Pad(testDiv, testGraph);

  it('has its clickHandler function called when its padElement is clicked',
    function() {
      spyOn(testPad, "clickHandler");
      simulateClick(testPad.padElement);
      //testPad.clickHandler();
      expect(testPad.clickHandler).toHaveBeenCalled();
  });

However, the test FAILS. Note that the event listener does get called (console.log writes successfully with a mouse click and with simulateClick), AND if I just call the testPad.clickHandler() directly Jasmine's spy can pick it up. But what happens during the actual test? Does the event handler invocation get transferred to a different object at runtime? What's the right way to do this?

2 Answers2

6

You are actually testing that GRAPH.padElement calls the supplied clickHandler and not that this.clickHandler of GRAPH.Pad is called by GRAPH.padElement. How I would do that is

var testDiv = document.createElement('div');
var clickHandlerSpy = jasmine.CreateSpy();
var padelement = padElement(testDiv , clickHandlerSpy);

  it('has its clickHandler function called when its padElement is clicked',
    function() {
      simulateClick(testPad.padElement);
      expect(clickHandlerSpy).toHaveBeenCalled();
  });

This may sound little different to what you are trying to achieve. But in ideal unit testing world you should be testing each unit independently, so I would first test that padElement does what it's supposed to do (as above) and then write another test to make sure GRAPH.Pad is passing correct handler to padElement. Now to do that I would not create padElement directly from within GRAPH.Pad but somehow inject it from outside and then mock it in the jasmine specs. If you are not clear on this part let me know and I can put some code together for you.

Suhas
  • 7,919
  • 5
  • 34
  • 54
3

To add a more generic explanation:

You need to spy before attaching the function as an event listener.

Some pseudo-code:

it("succeeds", function(){
  var o = {}
  o.f = function() { console.log("event caught!");} //your eventHandler
  spyOn(o, "f").and.callThrough(); //o.f is now a spy
  addEventListener('click', o.f); //spy listens for event 

  fireEvent("click"); //"event caught!" is written to console

  expect(o.f).toHaveBeenCalled(); //success, we're happy
});



it("fails", function(){
  var o = {}
  o.f = function() { console.log("event caught!");} //your eventHandler
  addEventListener('click', o.f); //your eventHandler function listens for event
  spyOn(o, "f").and.callThrough(); //o.f is now a spy

  fireEvent("click"); //"event caught!" is written to console

  expect(o.f).toHaveBeenCalled(); //fail, we waste a couple of hours not understanding why
});

So if you know your handler is being called, but the spy does not register as .tohaveBeenCalled() check what happens first: your spy or the assignment of the listener. The spy needs to come first.

Rafael Emshoff
  • 2,541
  • 1
  • 35
  • 47