Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test / mock a $timeout call?

How do I mock the timeout call, here?

$scope.submitRequest = function () {

    var formData = getData();

    $scope.form = JSON.parse(formData);

    $timeout(function () {
        $('#submitForm').click();            
    }, 2000);

};

I want to see timeout has been called with the correct function.

I would like an example of the spyon function mocking $timeout.

spyOn(someObject,'$timeout')
like image 524
GC_ Avatar asked Dec 11 '22 07:12

GC_


2 Answers

First of all, DOM manipulation should only be performed in directives. Also, it's better to use angular.element(...), than $(...). Finally, to do this, you can expose your element's click handler to the scope, spy on it, and check if that handler has been called:

$timeout.flush(2000);
$timeout.verifyNoPendingTasks();
expect(scope.myClickHandler).toHaveBeenCalled();

EDIT:

since that's a form and there is no ng-click handler, you can use ng-submit handler, or add a name to your form and do:

$timeout.flush(2000);
$timeout.verifyNoPendingTasks();
expect(scope.formName.$submitted).toBeTruthy();
like image 171
Frane Poljak Avatar answered Jan 18 '23 04:01

Frane Poljak


$timeout can be spied or mocked as shown in this answer:

beforeEach(module('app', ($provide) => {
  $provide.decorator('$timeout', ($delegate) => {
    var timeoutSpy = jasmine.createSpy().and.returnValue($delegate);
    // methods aren't copied automatically to spy
    return angular.extend(timeoutSpy, $delegate);
  });
}));

There's not much to test here, since $timeout is called with anonymous function. For testability reasons it makes sense to expose it as scope/controller method:

$scope.submitFormHandler = function () {
    $('#submitForm').click();            
};

...
$timeout($scope.submitFormHandler, 2000);

Then spied $timeout can be tested:

$timeout.and.stub(); // in case we want to test submitFormHandler separately
scope.submitRequest();
expect($timeout).toHaveBeenCalledWith(scope.submitFormHandler, 2000);

And the logic inside $scope.submitFormHandler can be tested in different test.

Another problem here is that jQuery doesn't work well with unit tests and requires to be tested against real DOM (this is one of many reasons why jQuery should be avoided in AngularJS applications when possible). It's possible to spy/mock jQuery API like shown in this answer.

$(...) call can be spied with:

var init = jQuery.prototype.init.bind(jQuery.prototype);
spyOn(jQuery.prototype, 'init').and.callFake(init);

And can be mocked with:

var clickSpy = jasmine.createSpy('click');
spyOn(jQuery.prototype, 'init').and.returnValue({ click: clickSpy });

Notice that it's expected that mocked function will return jQuery object for chaining with click method.

When $(...) is mocked, the test doesn't require #submitForm fixture to be created in DOM, this is the preferred way for isolated unit test.

like image 42
Estus Flask Avatar answered Jan 18 '23 05:01

Estus Flask