如何单元测试/模拟$ timeout调用?

时间:2018-03-21 18:04:03

标签: angularjs unit-testing jasmine mocking spyon

如何模拟超时通话?

$scope.submitRequest = function () {

    var formData = getData();

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

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

};

我希望看到使用正确的函数调用超时。

我想要一个模拟$ timeout的spyon函数的例子。

spyOn(someObject,'$timeout')

6 个答案:

答案 0 :(得分:5)

首先,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();

答案 1 :(得分:4)

$timeout可以进行间谍或嘲笑,如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);
  });
}));

这里测试的内容不多,因为使用匿名函数调用$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进行测试(这是为什么在可能的情况下应该在AngularJS应用程序中避免使用jQuery的众多原因之一)。可以窥探/模拟jQuery API,如this answer中所示。

$(...)可以通过以下方式调用:

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

请注意,模拟函数将返回jQuery对象以使用click方法进行链接。

当模拟$(...)时,测试不需要在DOM中创建#submitForm fixture,这是隔离单元测试的首选方法。

答案 2 :(得分:1)

为$ timeout provider创建模拟:

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

使用它:

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

现在你可以测试:

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

P.S。你最好在超时时测试功能结果。

答案 3 :(得分:1)

假设这段代码在控制器内或由$ controller在测试中创建,那么$ timeout可以在构造参数中传递。所以你可以这样做:

var timeoutStub = sinon.stub();
var myController = $controller('controllerName', timeoutStub);
$scope.submitRequest();
expect(timeoutStub).to.have.been.called;

答案 4 :(得分:1)

单位测试$超时,具有刷新延迟

您必须通过调用$ timeout.flush()

来刷新$ timeout服务的队列
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

答案 5 :(得分:1)

我完全同意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();
});

以下是两种方法的plunker:http://plnkr.co/edit/s5ls11