6

I am wanting to ensure that a object's method gets called as an event handler, when a custom jQuery event is triggered; but the unit test is seemingly returning a false-negative, as my implementation works well.

(This is part of a test suite that uses Twitter Flight & the Flight Jasmine extensions, but this is just a vanilla Jasmine spy.)

describe('listening for uiNeedsPlan event', function() {

var spy;
beforeEach(function() {
  spy = spyOn(this.component, 'getPlan');
  $(document).trigger('uiNeedsPlan');
});

it('gets the current plan', function() {
  expect(spy).toHaveBeenCalled();
});

});

This results in a failed spec:

Expected spy getPlan to have been called.

Here is a snippet from my code implementation of my Flight component (which works successfully):

this.after('initialize', function () {
  this.on(document, 'uiNeedsPlan', this.getPlan);
});
joecritch
  • 1,095
  • 2
  • 10
  • 25
  • 1
    Have you checked that your `this.after...` callback was called, using `debugger` or `console.log`. Btw. it is not a good idea to spy on functions of the object you wanna test. – Andreas Köberle Jun 23 '13 at 20:49

1 Answers1

11

Before I go in to the details of why this isn't working, I'd first like to point out that it's bad practice to test whether this method has been executed. You should be testing the component's interface - its input and output and its effect on the DOM. How it does what it does (which internal methods it calls, its internal state) is irrelevant.

As to why it's not working: When you create the spy, you are spying on the unbound version of the component method. When the component is instantiated, methods are bound to the instance and assigned to callbacks. It is impossible (afaik) to access these bound methods after the component is instantiated.

Instead, you need to spy on the the prototype before the component instance is attached to the DOM. Also, you don't seem to be calling setupComponent at all, so this.component won't exist yet anyway.

var spy;

beforeEach(function () {
    spy = spyOn(this.Component, 'getPlan');
    setupComponent();
    $(document).trigger('uiNeedsPlan');
});
it('executes getPlan', function() {
    expect(spy).toHaveBeenCalled();
});
Tom Hamshere
  • 502
  • 5
  • 11
  • Thank you Tom! The explanation of why not to use that in a unit test is still slightly baffling to me. I thought that testing event -> event was more of an midway test, rather than a unit test. But no doubt you're right :D Here's what I have so far; any thoughts would be appreciated. https://gist.github.com/joecritch/f6bbda18372a6c71393b – joecritch Jun 24 '13 at 09:37
  • 1
    The important thing is a thing's behaviour, not how it works. E.g.: When I turn up the volume control on an amplifier, I'm interested in whether the volume increases, not in whether all the internal components are in a particular order, named particular things, or a particular colour. This same test works for any implementation of the volume-control circuits and doesn't need to be altered even if I rebuild the amp from scratch. – Tom Hamshere Jun 24 '13 at 10:27
  • 1
    Basically, by testing internal structure, you're making work for yourself. If you ever come to refactor, you'll have to rebuild all your tests, and your tests aren't testing the only thing that matters - whether the component does what it is supposed to. It really helps if you write your tests before you write your component - that way you focus on what needs to happen as opposed to how it is going to happen. – Tom Hamshere Jun 24 '13 at 10:34
  • 1
    @TomHamshere, unit tests are to be executed in isolation. You're thinking e2e or integration test when you say "whether the component does what it is supposed to", afaik. Unit test should work on the isolated unit, even if the rest of the codebase is not present. – janesconference May 01 '14 at 17:17
  • 3
    I agree completely. I'm saying you should test the component's interface: the input and output, not the process it performs internally. It's the difference between testing what something does and how it does it. Treat the component as a black box. That way, when you change the internal workings, you don't have to alter your tests. – Tom Hamshere May 03 '14 at 00:29