【问题标题】:How to test an AngularJS controller value that is set within a promise using Sinon如何使用 Sinon 测试在 Promise 中设置的 AngularJS 控制器值
【发布时间】:2016-09-17 07:11:26
【问题描述】:

我在测试服务返回的承诺中设置的控制器值时遇到了麻烦。我正在使用 Sinon 来存根服务(Karma 运行测试,Mocha 作为框架,Chai 用于断言)。

我对快速解决问题的兴趣不如对问题的理解。我已经阅读了很多,我在代码和测试下面有一些笔记。

这是代码。

.controller('NavCtrl', function (NavService) {
  var vm = this;
  NavService.getNav()
    .then(function(response){
      vm.nav = response.data;
    });
})

.service('NavService', ['$http', function  ($http) {
  this.getNav = function () {
    return $http.get('_routes');
  };
}]);

这是测试:

describe('NavCtrl', function () {

  var scope;
  var controller;
  var NavService;
  var $q;

  beforeEach(module('nav'));

  beforeEach(inject(function($rootScope, $controller, _$q_, _NavService_){
    NavService = _NavService_;
    scope = $rootScope.$new();
    controller = $controller;
  }));

  it('should have some data', function () {

    var stub = sinon.stub(NavService, 'getNav').returns($q.when({
      response: {
        data: 'data'
      }
    }));

    var vm = controller("NavCtrl", {
      $scope: scope,
      NavService: NavService
    });

    scope.$apply();

    stub.callCount.should.equal(1);
    vm.should.be.defined;
    vm.nav.should.be.defined;

  });

});

正在调用存根,即测试通过,并且定义了vm,但vm.nav 永远不会获取数据并且测试失败。我认为,我如何处理存根的承诺是罪魁祸首。一些注意事项:

  • 基于reading elsewhere,我调用scope.$apply 来设置值,但由于没有将作用域注入到原始控制器中,我不肯定这会成功。这个article 指向angular docs on $q

  • 另一篇文章推荐 using $timeout 作为“真正完成承诺”的内容。这篇文章还建议使用“sinon-as-promised”,这是我在上面没有做的。我试过了,但没有发现区别。

  • 此 Stack Overflow 答案使用 scope.$root.$digest(),因为“如果您的范围对象的值来自承诺结果,您将需要调用 scope.$root.$digest()”。但同样,同样的测试失败。同样,这可能是因为我没有使用范围。

  • 关于stubing promise,我也试过sinon sandbox way,但结果是一样的。

  • 我尝试使用$scope 重写测试,以确保vm style 没有问题,但测试仍然失败。

最后,我可能是错的:存根和承诺可能不是问题,这是我错过的不同和/或明显的东西。

非常感谢任何帮助,如果我能澄清以上任何内容,请告诉我。

【问题讨论】:

  • 你说作用域没有注入到原始控制器上,但是你在实例化控制器时传递了它。如果您的控制器不使用注入的 $scope 在测试期间传递它不会让它使用它。大多数时候我使用$rootScope.apply() 来触发承诺解决。使用根范围消除了未触发承诺的可能性
  • angular 的 $q 服务在内部使用 $rootScope,他们建议使用 $rootScope apply - docs.angularjs.org/api/ng/service/$q#testing

标签: angularjs unit-testing promise mocha.js sinon


【解决方案1】:

抱歉,您只需要快速修复:

var stub = sinon.stub(NavService, 'getNav').returns($q.when({
  response: {
    data: 'data'
  }
}));

您的承诺被解析为包含response.data 的对象,而不仅仅是data 查看使用您的代码创建的这个 plunk:https://plnkr.co/edit/GL1Xuf?p=preview

扩展答案

我经常落入同样的陷阱。于是我开始分别定义一个方法返回的结果。然后,如果该方法是异步的,我将这个结果包装在像 $q.when(stubbedResult) 这样的承诺中,这允许我轻松地对实际结果运行预期,因为我将存根结果保存在变量中,例如

it('Controller should have some data', function () {

    var result = {data: 'data'};
    var stub = sinon.stub(NavService, 'getNav').returns($q.when(result));

    var vm = controller(/* initController */);

    scope.$apply();

    stub.callCount.should.equal(1);
    vm.nav.should.equal(result.data)
})

还有一些测试调试技巧会派上用场。最简单的事情是在控制台上转储一些数据,以检查某处返回的内容。当然,最好使用实际的调试器。

如何快速发现此类错误:

    1. $rootScope.apply() 行放置一个断点(就在它执行之前)
    1. 在控制器的 NavService.getNav().then 处理程序中放置一个断点,以查看它是否被调用以及它被调用的是什么
    1. 继续使用调试器执行$rootScope.$apply() 行。现在调试器应该命中上一步设置的断点 - 就是这样。

【讨论】:

    【解决方案2】:

    我认为你应该使用chai-as-promised

    然后从类似的承诺中断言

      doSomethingAsync().should.eventually.equal("foo");
    

    否则使用异步等待

     it('should have some data', async function () {
    
        await scope.$apply();
    
      });
    

    您可能需要移动然后getNav() 调用init 有点函数,然后针对该init 函数进行测试

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-29
      • 2011-10-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多