Как выполнить юнит-тест / макет вызова $timeout?

Как мне здесь высказать время ожидания?

$scope.submitRequest = function () {

    var formData = getData();

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

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

};

Я хочу видеть, что тайм-аут был вызван с правильной функцией.

Я хотел бы привести пример использования функции spyon для проверки $timeout.

spyOn(someObject,'$timeout')

6 ответов

Прежде всего, манипуляции с DOM должны выполняться только в директивах. Также лучше использовать angular.element(...), чем $(...). Наконец, для этого вы можете открыть обработчик щелчков вашего элемента в области видимости, следить за ним и проверять, был ли вызван этот обработчик:

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

РЕДАКТИРОВАТЬ:

так как это форма и нет обработчика ng-click, вы можете использовать обработчик ng-submit или добавить имя к вашей форме и сделать:

$timeout.flush(2000);
$timeout.verifyNoPendingTasks();
expect(scope.formName.$submitted).toBeTruthy();

$timeout можно шпионить или издеваться, как показано в этом ответе:

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);
  });
}));

Там не так много, чтобы проверить здесь, так как $timeout вызывается с анонимной функцией. По причинам, связанным с тестируемостью, имеет смысл представить его как метод области действия / контроллера:

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

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

Потом подсмотрел $timeout можно проверить:

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

И логика внутри $scope.submitFormHandler может быть проверено в другом тесте.

Другая проблема заключается в том, что jQuery плохо работает с модульными тестами и требует тестирования на реальном DOM (это одна из многих причин, по которой следует избегать jQuery в приложениях AngularJS, когда это возможно). Можно шпионить / смоделировать jQuery API, как показано в этом ответе.

$(...) Звонок может быть отслежен с:

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

И можно поиздеваться с:

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

Обратите внимание, что ожидается, что mocked функция вернет объект jQuery для объединения с click метод.

когда $(...) издевается, тест не требует #submitForm Приспособление, которое будет создано в DOM, это предпочтительный способ для изолированного модульного тестирования.

Unit Tesitng $timeout с задержкой сброса

Вы должны очистить очередь службы $ timeout, вызвав $timeout.flush()

describe('controller: myController', function(){
describe('showAlert', function(){
    beforeEach(function(){
        // Arrange
        vm.alertVisible = false;

        // Act
        vm.showAlert('test alert message');
    });

    it('should show the alert', function(){
        // Assert
        assert.isTrue(vm.alertVisible);
    });

    it('should hide the alert after 5 seconds', function(){
        // Act - flush $timeout queue to fire off deferred function
        $timeout.flush();

        // Assert
        assert.isFalse(vm.alertVisible);
    });
  })
});

Пожалуйста, проверьте эту ссылку http://jasonwatmore.com/post/2015/03/06/angularjs-unit-testing-code-that-uses-timeout

Создать макет для $timeout провайдера:

var f = () => {} 
var myTimeoutProviderMock = () => f;

Используй это:

beforeEach(angular.mock.module('myModule', ($provide) => {
  $provide.factory('$timeout', myTimeoutProviderMock);
}))

Теперь вы можете проверить:

spyOn(f);
expect(f).toHaveBeenCalled();

PS вам лучше проверить результат функции в тайм-ауте.

Я полностью согласен с ответом Frane Poljak. Вы должны обязательно следовать его пути. Второй способ сделать это - использовать сервис $timeout, как показано ниже:

describe('MainController', function() {
var $scope, $timeout;

beforeEach(module('app'));

beforeEach(inject(function($rootScope, $controller, $injector) {
  $scope = $rootScope.$new();
  $timeout = jasmine.createSpy('$timeout');
  $controller('MainController', {
    $scope: $scope,
    $timeout: $timeout
  });
}));

it('should submit request, function() {
  $scope.submitRequest();
  expect($timeout).toHaveBeenCalled();
});

Вот плункер, имеющий оба подхода: http://plnkr.co/edit/s5ls11

Предполагая, что фрагмент кода находится в контроллере или создается в тесте $controller, тогда в параметре конструкции можно передать $timeout. Так что вы можете просто сделать что-то вроде:

var timeoutStub = sinon.stub();
var myController = $controller('controllerName', timeoutStub);
$scope.submitRequest();
expect(timeoutStub).to.have.been.called;
Другие вопросы по тегам