【问题标题】:Stubbing factory being used in resolve用于解析的存根工厂
【发布时间】:2016-08-11 16:44:58
【问题描述】:

我正在尝试在控制器中对我的状态进行单元测试。我想要做的是将我的items 工厂存根,因为我有单独的单元测试来涵盖该功能。我很难让$injector 实际注入工厂,但似乎我让$provider 知道我想在实例化控制器时使用我的假项目对象。作为免责声明,我是 Angular 的新手,如果我的代码看起来很糟糕,我会喜欢一些建议。

目前,当我运行测试时,我收到消息:

Error: Unexpected request: GET /home.html
    No more request expected
        at $httpBackend (node_modules/angular-mocks/angular-mocks.js:1418:9)
        at n (node_modules/angular/angular.min.js:99:53)
        at node_modules/angular/angular.min.js:96:262
        at node_modules/angular/angular.min.js:131:20
        at m.$eval (node_modules/angular/angular.min.js:145:347)
        at m.$digest (node_modules/angular/angular.min.js:142:420)
        at Object.<anonymous> (spec/states/homeSpec.js:29:16)

我的模拟项目工厂似乎没有被注入到测试中。当我在要存根的方法中放置 console.log 行时,我看到该行正在被调用。

我要测试的代码如下:

angular.module('todo', ['ui.router'])
// this is the factory i want to stub out...
.factory('items', ['$http', function($http){
  var itemsFactory = {};
  itemsFactory.getAll = function() {
    // ...specifically this method
  };
  return itemsFactory;
}])
.controller('TodoCtrl', ['$scope', 'items', function($scope, items) {
  // Do things
}])
.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider){
  $stateProvider
    .state('home', {
      url: '/home',
      templateUrl: '/home.html',
      controller: 'TodoCtrl',
      resolve: {
        items: ['items', function(items){
          // this is the invocation that i want to use my stubbed method
          return items.getAll();
        }]
      }
    });

  $urlRouterProvider.otherwise('home');
}]);

我的测试如下所示:

describe('home state', function() {

  var $rootScope, $state, $injector, state = 'home';
  var getAllStub = sinon.stub();
  var items = {
    getAll: getAllStub
  };

  beforeEach(function() {
    module('todo', function($provide) {
      $provide.value('items', items);
    });

    inject(function(_$rootScope_, _$state_, _$injector_) {
      $rootScope = _$rootScope_;
      $state = _$state_;
      $injector = _$injector_;
    });
  });

  it('should resolve items', function() {
    getAllStub.returns('getAll');

    $state.go(state);
    $rootScope.$digest();
    expect($state.current.name).toBe(state);

    expect($injector.invoke($state.current.resolve.items)).toBe('findAll');
  });
});

提前感谢您的帮助!

【问题讨论】:

  • 你的模拟工厂很好。您收到错误消息,因为请求了“/home.html”模板。在单元测试中允许真正的路由器是一个坏主意,因为它打破了隔离并增加了更多的移动部件。我个人认为$stateProvider 等存根是一种更好的测试策略。您只需要确保使用预期的配置对象作为参数调用$stateProvider.state。如有必要,可以在集成/e2e 测试中使用真实路由器对应用进行测试。
  • 我喜欢这个建议。我尝试以与 items 相同的方式删除 $stateProvider$urlRouterProvider,但不幸的是,我仍然看到与上述相同的错误
  • 我已经发布了一个解释这一点的答案。请注意,在这种情况下,您不需要使用 $state.go 对其进行测试(并且在本规范中根本没有 $state 服务)。

标签: angularjs unit-testing angular-ui-router


【解决方案1】:

在单元测试中允许真正的路由器是一个坏主意,因为它打破了隔离并增加了更多的移动部件。我个人认为$stateProvider 等存根是一种更好的测试策略。

配置块中的顺序很重要,服务提供者应该在被注入其他模块之前被模拟。如果原始模块具有覆盖模拟服务提供者的 config 块,则应将模块存根:

  beforeAll(function () {
    angular.module('ui.router', []);
  });

  beforeEach(function () {
    var $stateProviderMock = {
      state: sinon.stub().returnsThis()
    };

    module(function($provide) {
      $provide.constant('$stateProvider', $stateProviderMock);
    });
    module('todo');
  });

您只需要确保使用预期的配置对象和参数调用 $stateProvider.state

  it('should define home state', function () {
    expect($stateProviderMock.state.callCount).to.equal(1);

    let [homeStateName, homeStateObj] = $stateProviderMock.state.getCall(0).args;

    expect(homeStateName).to.equal('home');
    expect(homeState).to.be.an('object');

    expect(homeState.resolve).to.be.an('object');
    expect(homeState.resolve.items).to.be.an('array');

    let resolvedItems = $injector.invoke(homeState.resolve.items);
    expect(items.getAll).to.have.been.calledOnce;
    expect(resolvedItems).to.equal('getAll');

    ...
  });

【讨论】:

  • 感谢您的回复!我仍然对单元测试的入口点在哪里感到困惑。以前我使用$rootScope.$digest() 来启动解析,但在这里我不确定我应该写哪一行来执行状态。
  • 这是实际规范,解析器由$injector.invoke 检查,如果解析器包含依赖于摘要的代码,您可以在之后触发摘要。您根本不需要在这里执行状态,只需检查状态是否使用$stateProvider 正确定义。 $state.go('home') 将状态更改为 'home' 的事实已经被 UI Router 测试过了!如果您还需要确保这些单元可以正常工作且没有意外,这是集成/e2e Protractor 测试的任务。
  • 知道了。最后一件事,当我尝试运行测试时,第一行出现错误expect($stateProviderMock.state.callCount).to.equal(1);,因为 callCount 仍然为零
  • 我猜是undefined,间谍应该是$stateProviderMock = { state: sinon.stub().returnsThis() }
  • 问题不是它未定义,而是$stateProviderMock.state 没有被调用。不幸的是,我仍然收到呼叫计数等于 0 而不是 1 的相同错误
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-10
  • 1970-01-01
  • 2013-01-24
  • 2011-09-03
相关资源
最近更新 更多