如何在不更改所有单元测试的情况下避免 "Unexpected request: GET" 在 app.run 周期内进行的调用

How to avoid "Unexpected request: GET" for a call made during app.run cycle without changing all my unit tests

我的 Angular 应用有一个获取登录用户信息的工厂:

.factory('UserInfo', function ($http) {
  return $http.get('/api/v1/whoami');
}

应用程序完成引导后立即注入此工厂。

在添加这个工厂之前,我所有的单元测试都通过了,但是在添加它之后,所有使用 $httpBackend 的单元测试都失败了,并显示错误消息:

Error: Unexpected request: GET /api/v1/whoami
Expected GET /api/v1/foobar

我可以在评论下方添加两行:

beforeEach(inject(function ($injector) {

  $httpBackend = $injector.get('$httpBackend');
  //these 2 lines fix all unit tests for this "describe"
  $httpBackend.expectGET('/api/v1/whoami').respond({foo: 'bar'});
  $httpBackend.flush();

}));

所有使用 $httpBackend 使它们再次通过的单元测试,或者为我的 UserInfo 工厂编写模拟,但我也希望能够测试工厂。

有没有办法避免这个错误,而不必在每个使用 $httpBackenddescribe 块上写这两行?

很多时候会有app需要的东西才能得到运行。比如用户认证、翻译等

对于这些情况,我通常在每个规范的 beforeEach 中添加这些期望。

describe('Directive test', function() {
   var $httpBackend;

   beforeEach(inject(function($injector) {
       // inject your stuff
       $httpBackend = $injector.get('$httpbackend')

       // set up app specific expectations
       $httpBackend.whenGET('/api/account').respond({});
   }))

   afterEach(function() {
        $httpBackend.verifyNoOutstandingExpectation();
        $httpBackend.verifyNoOutstandingRequest();
   });

   it('first test', function() {
        // do your tests
   })
})

我个人认为这种方法没有任何问题。围绕测试会有一些重复,但认为没关系。

有一次您需要承认,当测试开始并加载您的模块时,应用程序在技术上正在启动。这将启动您的一些应用程序代码。所以要么在测试中处理这些期望,要么改变你的应用程序。大多数时候在测试中处理这个问题会更好。

您肯定不想在每个单元测试中添加两行。如果 bootstrapping 逻辑发生变化(例如添加另一个 API 调用或清除缓存),您将不得不再次修改每个单元测试。

您可以将所有 bootstrap-support 代码(目前为 2 行)移动到 testUtils 服务中的 setup 函数中.然后,当 bootstrap 逻辑发生变化时,您将只需要更改一个函数。然而,你仍然会有脆弱的测试。红色测试应该表明被测试的单元出了问题。当 bootstrap 逻辑发生变化并且测试变红时,团队将需要争先恐后地找出错误的根本原因并对 testUtils.setup 进行必要的更新(这可能不仅仅是一个额外的 $httpBackend.whenGET)。

更好的解决方案是将所有组件(服务、指令、控制器、过滤器)移出主 (app) 模块。每个模块应包含单个组件或几个紧密耦合的组件。然后每个单元测试将只加载被测试的模块,绕过 bootstrap 代码。事实上,karma.conf.js根本不需要加载app.js。您也可以依靠这种方法将单元测试的速度提高一倍。

至于测试UserInfo,将其移动到自己的模块中并为其编写单元测试。

我刚刚带领一个团队进行了上述更改。工作时间不到一天,我们的单元测试现在速度更快,也更健壮。

有关模块化和单元测试的讨论,请参阅 Angular Developer Guide

在这种情况下,您的选择是:

  1. 如您所指$httpBackend.expectGET(...).respond(...)使用
  2. 在执行 UserInfo 的查找时使用 $provide to pass a different implementation

现在您可能最终会在多个地方重复此操作(取决于您的项目结构)。如果是这种情况,只需提取代码块,将其放入单独的 JS 文件中,然后将该文件包含在您的运行程序中(如果您使用 grunt-contrib-jasmine 之类的东西,请将所述文件添加到 vendor 列表中.)