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

Angularjs 如何单元测试/模拟$timeout调用?,angularjs,unit-testing,jasmine,mocking,spyon,Angularjs,Unit Testing,Jasmine,Mocking,Spyon,在这里,我如何模拟超时调用 $scope.submitRequest = function () { var formData = getData(); $scope.form = JSON.parse(formData); $timeout(function () { $('#submitForm').click(); }, 2000); }; 我想看看是否用正确的函数调用了timeout 我想要一个spyon函

在这里,我如何模拟超时调用

$scope.submitRequest = function () {

    var formData = getData();

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

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

};
我想看看是否用正确的函数调用了timeout

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

spyOn(someObject,'$timeout')

首先,DOM操作应该只在指令中执行。 另外,使用angular.element(…)比使用$(…)更好。 最后,要做到这一点,您可以将元素的click处理程序公开给作用域,监视它,并检查是否调用了该处理程序:

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

因为这是一个表单,并且没有ng click处理程序,所以您可以使用ng submit处理程序,或者在表单中添加一个名称,然后执行以下操作:

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

为$timeout提供程序创建模拟:

var f = () => {} 
var myTimeoutProviderMock = () => f;
使用它:

beforeEach(angular.mock.module('myModule', ($provide) => {
  $provide.factory('$timeout', myTimeoutProviderMock);
}))
现在您可以测试:

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

另外,您最好在timeout中测试函数的结果。

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

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

带刷新延迟的单元测试$超时

必须通过调用$timeout.flush()刷新$timeout服务的队列

请查看此链接

我完全同意您的看法。你一定要按照他的方式去做。第二种方法是模拟$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:

$timeout
可以被监视或模拟,如所示:

这里没有什么可测试的,因为
$timeout
是用匿名函数调用的。出于可测试性原因,将其公开为作用域/控制器方法是有意义的:

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

...
$timeout($scope.submitFormHandler, 2000);
然后可以测试spied
$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

$(…)
可以通过以下方式监视呼叫:

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,这是独立单元测试的首选方法。

提交表单时,没有单击处理程序。我想我可以有一个点击处理器,但那有点难看。
spyOn(f)
不起作用
myTimeoutProviderMock
将继续使用原始的
f
。我不推荐这种方法<代码>$timeout已经是ngMock moduleAgreed的一部分,不建议将此方法作为带有flush和verifyNoPendingTasks的ngMock装饰
$timeout
服务,因此鼓励使用此方法。这只是一种可能性。
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 });