【问题标题】:AngularJS: How to use $q in your config phase for a unit test?AngularJS:如何在配置阶段使用 $q 进行单元测试?
【发布时间】:2015-11-03 13:39:09
【问题描述】:

我有一个负责加载 config.json 文件的 Angular 服务。我想在我的 run phase 中调用它,所以我在我的 $rootContext 中设置了该 json,因此,将来每个人都可以使用它。

基本上,这就是我所拥有的:

angular.module('app.core', []).run(function(CoreRun) {
    CoreRun.run();
});

我的 CoreRun 服务在哪里:

 angular.module('app.core').factory('CoreRun', CoreRun);

 CoreRun.$inject = ['$rootScope', 'config'];

 function CoreRun($rootScope, config) {
   function run() {
     config.load().then(function(response) {
       $rootScope.config = response.data;
     });
   }    
   return {
     run: run
   };
}

这很好用,当我尝试 test 时问题就出现了。我想监视我的配置服务,所以它会返回一个虚假的承诺。但是,我无法做到这一点,因为在我的测试配置阶段,服务不可用,我无法注入 $q。

据我所知,我必须模拟我的配置服务的唯一机会是在配置阶段,因为它是由运行块调用的。

到目前为止,我发现的唯一方法是使用我非常不喜欢的 jQuery 生成 Promise。

beforeEach(module('app.core'));

var configSample;

beforeEach(module(function ($provide) {
   config = jasmine.createSpyObj('config', [ 'load' ]);
   config.load.and.callFake(function() {
     configSample = { baseUrl: 'someurl' };        
     return jQuery.Deferred().resolve({data: configSample}).promise();
   });
   provide.value('config', config);
}));

it('Should load configuration using the correspond service', function() {
  // assert
  expect(config.load).toHaveBeenCalled();
  expect($rootScope.config).toBe(configSample);
});

有没有办法做出更正确的解决方法?

编辑: 可能值得一提的是,这只是在对我的运行块进行单元测试时出现的问题。

【问题讨论】:

  • 看起来相似但完全不同。您所指的是关于在真实模块的配置阶段注入 $q 。这是关于在单元测试的配置阶段将 $q 注入运行块中,因此您可以模拟它以便能够测试您的运行块。

标签: javascript angularjs unit-testing jasmine promise


【解决方案1】:

似乎不可能以正确的方式注入$q,因为run() 块中的函数会立即触发。 run() 块在 Angular 中被认为是配置阶段,因此测试中的 inject() 仅在配置块之后运行,因此即使您在测试中 inject() $q ,它也会是 undefined,因为 run() 先执行.

一段时间后,我能够通过一种非常肮脏的解决方法在module(function ($provide) {}) 块中获得$q。这个想法是创建一个额外的角度模块,并在您的应用程序模块之前将其包含在测试中。这个额外的模块还应该有一个run() 块,它将$q 发布到全局命名空间。注入器会先调用额外模块的run(),然后再调用app模块的run()

angular.module('global $q', []).run(function ($q) {
    window.$q = $q;
});

describe('test', function () {

    beforeEach(function () {

        module('global $q');

        module('app.core');

        module(function ($provide) {
            console.log(window.$q); // exists
        });

        inject();

    });
});

这个额外的模块可以作为测试套件的单独文件包含在规范文件之前。如果您将模块放在测试所在的同一文件中,那么您不需要使用全局 window 变量,而只需使用文件中的变量。

Here is a working plunker(参见“script.js”文件)

第一个解决方案(没有解决问题):

在这种情况下,您实际上可以使用$q,但您必须将其注入测试文件。在这里,您不会真正将其注入到被测单元中,而是直接注入到测试文件中,以便能够在测试中使用它。所以它实际上并不依赖于被测单元的类型:

// variable that holds injected $q service
var $q;

beforeEach(module(function ($provide) {
    config = jasmine.createSpyObj('config', [ 'load' ]);

    config.load.and.callFake(function() {
        var configSample = { baseUrl: 'someurl' };

        // create new deferred obj
        var deferred = $q.defer();

        // resolve promise
        deferred.resolve({ data: configSample });

        // return promise
        return deferred.promise;
   });

   provide.value('config', config);
}));

// inject $q service and save it to global (for spec) variable
// to be able to access it from mocks
beforeEach(inject(function (_$q_) {
    $q = _$q_;
}));

资源:

还有一点需要注意:config 阶段和 run 阶段是两个不同的东西。 Config 块只允许使用提供者,但在 run 块中你可以注入几乎所有东西(提供者除外)。更多信息在这里 - Module Loading & Dependencies

【讨论】:

  • 是的,当然。我知道他们之间的区别。我只想提一下,理想情况下,我需要在测试中的配置阶段注入 $q,因为它在我的运行块之前运行。
  • 感谢您的回答。我会尽快尝试。但是,我想记住已经测试过它并且它不起作用,因为第二个 beforeach 是在我的运行块已经执行后执行的,因此,在那一刻 $q 是未定义的。
  • 确实不行。这可能是因为我需要在我的运行块执行时注入$q(由beforeEach(module('app.core')); 触发),而此时beforeEach(inject(function (_$q_) 还没有发生。
  • @jbernal,是的,你是对的,我错了,$qundefinedinject()Core.run() 被执行之后运行,在这种情况下似乎没有办法在provide() 中使用$q。但是我能够以一种非常棘手的方式解决它,我已经更新了帖子。
  • 很高兴看到您找到了解决方案!正如您所说,它不是最好的,但它可以工作并且用于测试,因此您不会弄脏源代码。如果您的项目中也已经有了它,那么像我一样使用 jQuery 仍然有效。每个人都可以选择他更喜欢的一个:) 感谢您的帮助!
猜你喜欢
  • 2013-09-29
  • 2020-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多