Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing Mouseover event in D3 with Sinon

I'm having trouble trying to get my test to pass. I would like to be able to use a spy to check whether or not the mouseover event was correctly called. Currently I am getting the following error, "Error: expected on to have been called at least once but was never called". Part of the confusion on my part relates to the differences between d3 and jQuery selectors, I'm more than happy to use the latter, but I'm not sure how to correctly use the former in a test to get the result I want.

My dependencies are d3, jQuery, mocha, chai, sinon, and sinon-chai.

Relevant code from my index.html file,

<script src="fixtures.js"></script>
<div id="mocha"></div>
<script src="mocha.js"></script>
<script src="chai.js"></script>
<script src="sinon-chai.js"></script>
<script src="sinon-1.10.2.js"></script>
<script>
    mocha.ui('bdd');
    mocha.reporter('html');
    var expect = chai.expect;
 </script>
 <script src="tests.js"></script>
 <script>
    mocha.run();
</script>

fixtures.js,

var path = svg.selectAll("path")
          .data(pie(data))
          .enter().append("path").attr("class", "path")
          .style("fill", function(d, i) { return color(i); })
          .attr("d", arc)
          .on("mouseover", function() { d3.select(this).style("fill", "#ff0000"); })
          .on("mouseout" , function() { d3.selectAll("path").style("fill", function(d, i) { return color(i); }); });

//  I wanted to test my understanding of d3 selectors
var path_one = d3.select('.path').attr("id", "path_one"); 

tests.js,

describe('Donut Chart', function() {
    describe("Mouseover events", function() {
        it("should call on('mouseover')", function () {
            var spy = sinon.spy(path, "on");
            $('path').mouseenter();
            sinon.assert.called(spy);
        });
    });
});
like image 859
tpgmartin Avatar asked Jun 05 '14 10:06

tpgmartin


1 Answers

There's a few issues here; we can sort out your syntax, but we also have to sort out the intention.

The error message "Error: expected on to have been called at least once but was never called" is accurate. on isn't called during your test. It's only called in your fixtures, to set up the event handlers. You're also triggering a mouseenter event but your listeners are for mouseover and mouseout. In the real world, you'd get a mouseover very soon after a mouseenter event, but when you fake it with jQuery that won't happen. jQuery is a non-starter anyway; see below.


You could try to fix this by changing them from anonymous functions to something named, like this:

var mouseOverHandler = function() { 
    d3.select(this).style('fill', '#ff0000');
};

And then bind it to your path with path.on('mouseover', mouseOverHandler). You'd think you can now spy on mouseOverHandler, but that won't work either. Your function will be bound when you call on, so swapping it out for a Sinon spy later won't have any effect.

jQuery triggering won't work with D3

Another problem is you can't use jQuery to trigger D3 events because jQuery events aren't DOM events. So you should replace your call to $('path').mouseenter() to something like document.getElementById('path_one').dispatchEvent(new MouseEvent('mouseover')); (Noting that this is changing the code from "trigger mouseenter on all paths" to "trigger mouseover on the element with id path_one").

Testing the wrong thing

You could futz around with refactoring so you can swap out your callback with something you can spy, but fundamentally you're testing the wrong thing. Essentially, you're trying to write a test for D3; "When I add an event listener, make sure that event listener gets called." Instead, you should test your actual code's behaviour: "When someone mouses over the chart, its colour should change".

If you really want to assert that your event handler was bound, you could do that:

expect(path.on('mouseover')).to.be.a('function')

But if you want to make sure your colour is changed, you want your test to be as follows:

describe("Mouseover events", function() {
    it("should update the colours when a user mouses over the paths", function () {
        var oldColor = path.style('fill');
        document.getElementById('path_one').dispatchEvent(new MouseEvent('mouseover'));
        expect(path.style('fill')).to.not.equal(oldColor);
    });
});
like image 66
Alex P Avatar answered Sep 18 '22 17:09

Alex P