cwo cwo - 1 month ago 5
Javascript Question

Testing JQuery's "on" method fires callback function

I am trying to test following code with Jasmine:

import $ from 'jquery';

export function createCheckIcon (handleClick) {
return $('<span>').attr('data-element', 'check').addClass('icon--check').on('click', handleClick);
}

export function createCrossIcon (handleClick) {
return $('<span>').attr('data-element', 'cross').addClass('icon--cross').on('click', handleClick);
}


My test looks like this:

import $ from 'jquery';

import { createCrossIcon, createCheckIcon } from './input-icons';

describe('input icons', () => {

let handleMock = { handleClick: () => true };

it('can create a cross icon', () => {
let $crossIcon = createCrossIcon();
expect($crossIcon).toHaveAttr('data-element', 'cross');
expect($crossIcon).toHaveClass('icon--cross');
});

it('cross icon handles click event', () => {
let $crossIcon = createCrossIcon(handleMock.handleClick);
spyOn(handleMock, 'handleClick');
$crossIcon.trigger('click');
expect(handleMock.handleClick).toHaveBeenCalled();
});

it('can create a check icon', () => {
let $checkIcon = createCheckIcon();
expect($checkIcon).toHaveAttr('data-element', 'check');
expect($checkIcon).toHaveClass('icon--check');
});
});


Unfortunately my test will fail with:

Expected spy handleClick to have been called.


Maybe I still have a basic misunderstanding how to test if the click handle was fired. Does anyone see where my problem is? I also tried jasmine-jquery to get this working but still no luck.

Answer

The problem

You have a misconception of how variables work in JavaScript.

You see, when you pass handleClick in createCrossIcon(handleMock.handleClick) you are actually passing a link to the function, not the function itself. And then you pass this link to the .on method.

Then you set a Jasmine spy - spyOn(handleMock, 'handleClick') and what happens is Jasmine replaces the handleClick property of handleMock object with a utility function that remembers all the times it was called.

But jQuery will never know about your intentions, because it has already decided to call the original function on click.

The solution

What you need to do is simply change the order of the lines so that spyOn will come before creating an icon:

spyOn(handleMock, 'handleClick');
let $crossIcon = createCrossIcon(handleMock.handleClick);

This way you will pass a spy as a callback, not the actual callback function.

Comments