【问题标题】:Mock angular factory that uses $q in Jasmine在 Jasmine 中使用 $q 的模拟角工厂
【发布时间】:2015-09-09 22:28:30
【问题描述】:

之前肯定有人问过这个问题,但我找不到。我需要模拟一个工厂,但模拟本身需要使用 $q,而关于在注入()之后调用模块(),我最终陷入了先有鸡还是先有蛋的境地。

我查看了这个question,它建议做一个 spyOn,它适用于 services 因为它是一个单例,但我在返回的函数上调用 new我的工厂,每次都创建一个新实例,这样就不行了……

var app = angular.module('app', []);

app.factory('MyDependencyFactory', function() {
  return function() {
    this.doPromise = function () {
      var defer = $q.defer();
      //obviously more complicated.
      defer.resolve();
      return defer.promise;   
    }
  }
});

app.factory('MyConsumingFactory', function(MyDependencyFactory) {
 return function() {
   var dependency = new MyDependencyFactory();
   this.result;

   this.doSomething = function () {
     dependency.doPromise().then(
       function (data) {
         this.result = data;
       },
       function (error) {
         console.log(error);
       }
       );
   }
  }
});

茉莉花测试:

describe('MyConsumingFactory', function() {
  var MyConsumingFactory;

  beforeEach(function () {
    module('app');

    inject( function (_MyConsumingFactory_) {
      MyConsumingFactory = _MyConsumingFactory_;
    });

    inject( function ($q) {
      mockMyDependencyFactory = function () {
        this.doPromise = function (data) {
            var defer = $q.defer();
            defer.resolve('mock data');
          };
        };
    });

    module( function ($provide) {
      $provide.factory('MyDependencyFactory', mockMyDependencyFactory);
    });
  });

  it('works correctly', function () {
    MyConsumingFactory.doSomething();
    $rootScope.$apply();
    expect(MyConsumingFactory.result).toEqual('mock data');
  });

});

我需要我的 mockMyDependencyFactory 来使用 $q,所以我需要将它包装在 inject(function(...) 中,并且我需要在调用 module(function ($provide) {... 这当然给了我:

错误:注入器已创建,无法注册模块!

对我如何解决这个问题有什么建议吗?

或者,如果您认为我的设计有缺陷(我想我可以实例化一个 MyDependencyFactory 并在实例化 MyConsumingFactory 期间传递它,而不是使用 angular 的 DI?)我全神贯注 :)

【问题讨论】:

    标签: javascript angularjs jasmine


    【解决方案1】:

    首先,您对module() 的所有调用都应在inject() 之前,否则您将收到此错误:Injector already created, can not register a module! 即您应该在将模块注入代码之前注册它们。知道了这一点,我们需要在注入之前模拟MyDependencyFactory,但是如果$q 仅在inject() 中可用,我们如何在其中获得$q?实际上,在 Angular 测试中,将注入的服务分配给测试套件中的全局变量,然后在所有场景中使用它是一种常见的技术:

    describe('some suite', function () {
    
        // "global" variables for injected services
        var $rootScope, $q;
    
        beforeEach(function () {
    
            module('app');
    
            module(function($provide) {
    
                $provide.factory('MyDependencyFactory', function () {
                    return function () {
                        this.doPromise = function (data) {
                            // use "globals"
                            var defer = $q.defer();
                            defer.resolve('mock data');
                            return defer.promise;
                        };
                    };   
                });
    
            });
    
            inject(function (_$rootScope_, _$q_) {
                // assign to "globals"
                $rootScope = _$rootScope;
                $q = _$q;
            });
        });
    
        // ....
    
    });
    

    您可以在$provide 块中使用$q 的原因是它不会立即使用,它只会在您调用模拟方法或创建模拟对象的实例时使用。到那时,它将被注入并分配给全局变量$q,并具有适当的值。

    如果你想用不同的值多次解决你的承诺,你可以做的另一个技巧是创建一个全局变量 defer 并不在特定方法中初始化它,而是在一些 beforeEach 块中,然后执行defer.resolve('something') 在您的场景中,在此特定场景中具有您想要的值。

    Here you can see a working example of your code,我做了一些额外的修复以使其正常工作(有 cmets)。

    注意:我说的是“全局”变量,但它实际上并不是 JS 术语中的全局变量,而是特定测试套件中的全局变量。

    【讨论】:

    • 此示例有效,但如果 app.run() 块调用“MyDependencyFactory”(在我的真实世界场景中发生),那么在测试中它会得到模拟,由于时间 $q 未定义: plnkr.co/edit/eTGrCRwYA8fSm4yMXw2J?p=preview
    • 我在 app.run() 中通过一个丑陋的 hack 解决了这个问题,它检查一个全局(真正的全局)变量以确定它是否在测试模式下运行。
    • 您也可以将$q 提供给一个mock,作为测试中工厂构造函数的参数:$provide.factory('MyDependencyFactory', function ($q) { ... }),它将像在真实应用中一样被注入。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-04
    • 1970-01-01
    • 2020-03-18
    • 2015-07-07
    • 1970-01-01
    • 2014-09-20
    • 1970-01-01
    相关资源
    最近更新 更多