Angular.js中基于单元测试承诺的代码


67

我在尝试在Angularjs中测试基于承诺的代码时遇到了困难。

我的控制器中有以下代码:

    $scope.markAsDone = function(taskId) {
        tasksService.removeAndGetNext(taskId).then(function(nextTask) {
            goTo(nextTask);
        })
    };

    function goTo(nextTask) {
        $location.path(...);
    }

我想对以下情况进行单元测试:

  • 什么时候markAsDone被调用tasksService.removeAndGetNext
  • tasksService.removeAndGetNext是做了应该改变的位置(调用goTo

在我看来,没有简单的方法可以分别测试这两种情况。

我测试第一个是:

var noopPromise= {then: function() {}}
spyOn(tasksService, 'removeAndGetNext').andReturn(noopPromise);

现在,要测试第二种情况,我需要创建另一个永远都是假的承诺resolved。这一切都非常乏味,并且有很多样板代码。

还有其他测试方法吗?还是我的设计有异味?


接受的解决方案对我不起作用。该做的:stackoverflow.com/questions/21895684/...
sibidiba

Answers:


107

您仍然需要模拟服务并返回承诺,但是您应该使用真实的承诺,因此您无需实现其功能。使用beforeEach创建已被兑现承诺,并嘲笑服务,如果你需要它总是被解决。

var $rootScope;

beforeEach(inject(function(_$rootScope_, $q) {
  $rootScope = _$rootScope_;

  var deferred = $q.defer();
  deferred.resolve('somevalue'); //  always resolved, you can do it from your spec

  // jasmine 2.0
  spyOn(tasksService, 'removeAndGetNext').and.returnValue(deferred.promise); 

  // jasmine 1.3
  //spyOn(tasksService, 'removeAndGetNext').andReturn(deferred.promise); 

}));

如果您希望在每个it块中使用不同的值来解析它,则只需将延迟的变量公开给局部变量,然后在规范中对其进行解析即可。

当然,您可以按原样保留测试,但是这里有一些非常简单的规范可以向您展示它如何工作。

it ('should test receive the fulfilled promise', function() {
  var result;

  tasksService.removeAndGetNext().then(function(returnFromPromise) {
    result = returnFromPromise;
  });

  $rootScope.$apply(); // promises are resolved/dispatched only on next $digest cycle
  expect(result).toBe('somevalue');
});

你能解释一下$ rootScope = $ rootScope的目的吗?在你的第一个例子中?
aamiri 2013年

2
我正在做的是注入$rootScope(如果在前后插入_,它将被忽略)并通过局部变量将其公开。这样,您无需将其注入每个单独的规范中,因为它们都可能会使用它。
Caio Cunha 2013年

13
+1我错过了这个$ rootScope。$ apply()-非常有帮助,谢谢
Mike

@CaioToOn是否可以通过某种方式在不应用范围的情况下发送承诺?
Federico Nafria 2014年

3
根据记录,整个var deferred = $q.defer(); deferred.resolve('something'); returnValue(deferred.promise)过程可以简化为returnValue($q.when('something'))
charlespwd 2015年

4

另一种方法是从我正在测试的控制器中直接采取以下方法:

var create_mock_promise_resolves = function (data) {
    return { then: function (resolve) { return resolve(data); };
};

var create_mock_promise_rejects = function (data) {
    return { then: function (resolve, reject) { if (typeof reject === 'function') { return resolve(data); } };
};

var create_noop_promise = function (data) {
    return { then: function () { return this; } };
};

2
您的promise模拟将要同步运行resolve()或reject()函数,但是在AngularJs中,它总是异步完成的。因此,我不推荐这种解决方案,因为它不能准确地测试代码。
Kayhadrin 2014年

1
在单元测试中,您希望能够同步运行异步代码。这就是为什么$httpBackend在Angular测试环境中会自动模拟出类似服务的原因。
yangmillstheory

其实,我完全明白你的意思。这些承诺将自动刷新。您希望能够按命令刷新它们。
yangmillstheory 2014年

0

作为另一种选择,您可以避免$digest使用Q库(https://github.com/kriskowal/q)来代替调用,$q例如:

beforeEach(function () {
    module('Module', function ($provide) {
        $provide.value('$q', Q); 
    });
});

这样,可以在$ digest循环之外解决/拒绝promise。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.