草庐IT

javascript - 在 Angular 单元测试中应该如何处理运行 block ?

coder 2024-12-20 原文

我的理解是,当您在 Angular 单元测试中加载模块时,run block 会被调用。

我想如果你正在测试一个组件,你不会希望同时测试 run block ,因为 unit 测试应该只是测试一个单元。是真的吗?

如果是这样,有没有办法阻止 run block 运行?我的研究使我认为答案是“否”,并且 run block 总是在加载模块时运行,但也许有一种方法可以覆盖它。如果没有,我将如何测试 run block ?

运行 block :

function run(Auth, $cookies, $rootScope) {
  $rootScope.user = {};
  Auth.getCurrentUser();
}

Auth.getCurrentUser:

getCurrentUser: function() {
  // user is logged in
  if (Object.keys($rootScope.user).length > 0) {
    return $q.when($rootScope.user);
  }
  // user is logged in, but page has been refreshed and $rootScope.user is lost
  if ($cookies.get('userId')) {
    return $http.get('/current-user')
      .then(function(response) {
        angular.copy(response.data, $rootScope.user);
        return $rootScope.user;
      })
    ;
  }
  // user isn't logged in
  else  {
    return $q.when({});
  }
}

auth.factory.spec.js

describe('Auth Factory', function() {
  var Auth, $httpBackend, $rootScope, $cookies, $q;
  var user = {
    username: 'a',
    password: 'password',
  };
  var response = {
    _id: 1,
    local: {
      username: 'a',
      role: 'user'
    }
  };

  function isPromise(el) {
    return !!el.$$state;
  }

  beforeEach(module('mean-starter', 'ngCookies', 'templates'));
  beforeEach(inject(function(_Auth_, _$httpBackend_, _$rootScope_, _$cookies_, _$q_) {
    Auth = _Auth_;
    $httpBackend = _$httpBackend_;
    $rootScope = _$rootScope_;
    $cookies = _$cookies_;
    $q = _$q_;
  }));
  afterEach(function() {
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();
  });

  it('#signup', function() {
    $rootScope.user = {};
    $httpBackend.expectPOST('/users', user).respond(response);
    spyOn(angular, 'copy').and.callThrough();
    spyOn($cookies, 'put').and.callThrough();
    var retVal = Auth.signup(user);
    $httpBackend.flush();
    expect(angular.copy).toHaveBeenCalledWith(response, $rootScope.user);
    expect($cookies.put).toHaveBeenCalledWith('userId', 1);
    expect(isPromise(retVal)).toBe(true);
  });

  it('#login', function() {
    $rootScope.user = {};
    $httpBackend.expectPOST('/login', user).respond(response);
    spyOn(angular, 'copy').and.callThrough();
    spyOn($cookies, 'put').and.callThrough();
    var retVal = Auth.login(user);
    $httpBackend.flush();
    expect(angular.copy).toHaveBeenCalledWith(response, $rootScope.user);
    expect($cookies.put).toHaveBeenCalledWith('userId', 1);
    expect(isPromise(retVal)).toBe(true);
  });

  it('#logout', function() {
    $httpBackend.expectGET('/logout').respond();
    spyOn(angular, 'copy').and.callThrough();
    spyOn($cookies, 'remove');
    Auth.logout();
    $httpBackend.flush();
    expect(angular.copy).toHaveBeenCalledWith({}, $rootScope.user);
    expect($cookies.remove).toHaveBeenCalledWith('userId');
  });

  describe('#getCurrentUser', function() {
    it('User is logged in', function() {
      $rootScope.user = response;
      spyOn($q, 'when').and.callThrough();
      var retVal = Auth.getCurrentUser();
      expect($q.when).toHaveBeenCalledWith($rootScope.user);
      expect(isPromise(retVal)).toBe(true);
    });
    it('User is logged in but page has been refreshed', function() {
      $cookies.put('userId', 1);
      $httpBackend.expectGET('/current-user').respond(response);
      spyOn(angular, 'copy').and.callThrough();
      var retVal = Auth.getCurrentUser();
      $httpBackend.flush();
      expect(angular.copy).toHaveBeenCalledWith(response, $rootScope.user);
      expect(isPromise(retVal)).toBe(true);
    });
    it("User isn't logged in", function() {
      $rootScope.user = {};
      $cookies.remove('userId');
      spyOn($q, 'when').and.callThrough();
      var retVal = Auth.getCurrentUser();
      expect($q.when).toHaveBeenCalledWith({});
      expect(isPromise(retVal)).toBe(true);
    });
  });
});

尝试 1:

beforeEach(module('mean-starter', 'ngCookies', 'templates'));
beforeEach(inject(function(_Auth_, _$httpBackend_, _$rootScope_, _$cookies_, _$q_) {
  Auth = _Auth_;
  $httpBackend = _$httpBackend_;
  $rootScope = _$rootScope_;
  $cookies = _$cookies_;
  $q = _$q_;
}));
beforeEach(function() {
  spyOn(Auth, 'getCurrentUser');
});
afterEach(function() {
  expect(Auth.getCurrentUser).toHaveBeenCalled();
  $httpBackend.verifyNoOutstandingExpectation();
  $httpBackend.verifyNoOutstandingRequest();
});

这行不通。 run block 在加载模块时运行,因此在设置 spy 之前调用 Auth.getCurrentUser()

Expected spy getCurrentUser to have been called.

尝试 2:

beforeEach(inject(function(_Auth_, _$httpBackend_, _$rootScope_, _$cookies_, _$q_) {
  Auth = _Auth_;
  $httpBackend = _$httpBackend_;
  $rootScope = _$rootScope_;
  $cookies = _$cookies_;
  $q = _$q_;
}));
beforeEach(function() {
  spyOn(Auth, 'getCurrentUser');
});
beforeEach(module('mean-starter', 'ngCookies', 'templates'));
afterEach(function() {
  expect(Auth.getCurrentUser).toHaveBeenCalled();
  $httpBackend.verifyNoOutstandingExpectation();
  $httpBackend.verifyNoOutstandingRequest();
});

这不起作用,因为在加载我的应用模块之前无法注入(inject) Auth

Error: [$injector:unpr] Unknown provider: AuthProvider <- Auth

尝试 3:

如您所见,这里存在先有鸡还是先有蛋的问题。我需要在加载模块之前注入(inject) Auth 并设置 spy ,但我不能,因为在加载模块之前无法注入(inject) Auth。

This博客文章提到先有鸡还是先有蛋的问题,并提供了一个有趣的潜在解决方案。作者建议我应该在加载模块之前使用 $provide 手动创建我的 Auth 服务。因为我正在创建服务,而不是注入(inject)它,所以我可以在加载模块之前完成它,并且我可以设置 spy 。然后在加载模块时,它会使用这个创建的模拟服务。

这是他的示例代码:

describe('example', function () {
    var loggingService;
    beforeEach(function () {
        module('example', function ($provide) {
            $provide.value('loggingService', {
                start: jasmine.createSpy()
            });
        });
        inject(function (_loggingService_) {
            loggingService = _loggingService_;
        });
    });
    it('should start logging service', function() {
        expect(loggingService.start).toHaveBeenCalled();
    });
});

问题是我需要我的 Auth 服务!我只想将模拟的用于 run block ;我需要在别处使用真正的 Auth 服务,以便对其进行测试。

我想我可以使用 $provide 创建实际的 Auth 服务,但感觉不对。


最后一个问题 - 无论我最终使用什么代码来处理这个 run block 问题,我有没有办法将它提取出来,这样我就不必重新编写它我的每个规范文件?我能想到的唯一方法是使用某种全局函数。


auth.factory.js

angular
  .module('mean-starter')
  .factory('Auth', Auth)
;

function Auth($http, $state, $window, $cookies, $q, $rootScope) {
  return {
    signup: function(user) {
      return $http
        .post('/users', user)
        .then(function(response) {
          angular.copy(response.data, $rootScope.user);
          $cookies.put('userId', response.data._id);
          $state.go('home');
        })
      ;
    },
    login: function(user) {
      return $http
        .post('/login', user)
        .then(function(response) {
          angular.copy(response.data, $rootScope.user);
          $cookies.put('userId', response.data._id);
          $state.go('home');
        })
      ;
    },
    logout: function() {
      $http
        .get('/logout')
        .then(function() {
          angular.copy({}, $rootScope.user);
          $cookies.remove('userId');
          $state.go('home');
        })
        .catch(function() {
          console.log('Problem logging out.');
        })
      ;
    },
    getCurrentUser: function() {
      // user is logged in
      if (Object.keys($rootScope.user).length > 0) {
        return $q.when($rootScope.user);
      }
      // user is logged in, but page has been refreshed and $rootScope.user is lost
      if ($cookies.get('userId')) {
        return $http.get('/current-user')
          .then(function(response) {
            angular.copy(response.data, $rootScope.user);
            return $rootScope.user;
          })
        ;
      }
      // user isn't logged in
      else  {
        return $q.when({});
      }
    }
  };
}

编辑 - 失败尝试 + 成功尝试:

beforeEach(module('auth'));
beforeEach(inject(function(_Auth_) {
  Auth = _Auth_;
  spyOn(Auth, 'requestCurrentUser');
}));
beforeEach(module('mean-starter', 'ngCookies', 'templates'));
beforeEach(inject(function(_Auth_, _$httpBackend_, _$rootScope_, _$cookies_, _$q_) {
  // Auth = _Auth_;
  $httpBackend = _$httpBackend_;
  $rootScope = _$rootScope_;
  $cookies = _$cookies_;
  $q = _$q_;
}));
// beforeEach(function() {
//   spyOn(Auth, 'getCurrentUser');
// });
afterEach(function() {
  expect(Auth.getCurrentUser).toHaveBeenCalled();
  $httpBackend.verifyNoOutstandingExpectation();
  $httpBackend.verifyNoOutstandingRequest();
});

我不确定为什么这行不通(独立于使用 inject 两次的问题)。

我试图绕开必须使用 $provide 的问题,因为最初我觉得它很老套/很奇怪。不过再想一想,我现在觉得 $provide 很好,按照你的建议使用 mock-auth 太棒了!!!两者都为我工作。

auth.factory.spec.js 中,我刚刚加载了 auth 模块(我称它为 auth,而不是 mean-auth) 而无需加载 mean-starter。这没有 run block 问题,因为该模块没有 run block 代码,但它允许我测试我的 Auth工厂。在其他地方,这有效:

beforeEach(module('mean-starter', 'templates', function($provide) {
  $provide.value('Auth', {
    requestCurrentUser: jasmine.createSpy()
  });
}));

奇妙的 mock-auth 解决方案也是如此:

auth.factory.mock.js

angular
  .module('mock-auth', [])
  .factory('Auth', Auth)
;

function Auth() {
  return {
    requestCurrentUser: jasmine.createSpy()
  };
}

user.service.spec.js

beforeEach(module('mean-starter', 'mock-auth', 'templates'));

最佳答案

My understanding is that when you load your module in Angular unit tests, the run block gets called.

正确。

I'd think that if you're testing a component, you wouldn't want to simultaneously be testing the run block, because unit tests are supposed to just test one unit. Is that true?

也是正确的,因为现在您正在有效地测试 Auth 和您的运行 block 的集成,并且彼此之间没有隔离。

If so, is there a way to prevent the run block from running? My research leads me to think that the answer is "no", and that the run block always runs when the module is loaded, but perhaps there's a way to override this. If not, how would I test the run block?

作为实现,不,你不能阻止运行 block 运行。但是,由于您的问题最终是模块化之一,因此进行一些小的重构仍然是可能的。在看不到您的模块声明的情况下,我想它看起来像这样:

angular.module('mean-starter', ['ngCookies'])

  .factory('Auth', function($cookies) {
    ...
  });

  .run(function(Auth, $rootScope) {
    ...
  });

此模式可以分解为模块以支持可测试性(和模块可重用性):

angular.module('mean-auth', ['ngCookies'])

  .factory('Auth', function() {
    ...
  });

angular.module('mean-starter', ['mean-auth'])

  .run(function(Auth, $rootScope) {
    ...
  });

这现在允许您通过仅将 mean-auth 模块加载到其测试中来单独测试您的 Auth 工厂。

虽然这解决了运行 block 干扰 Auth 单元测试的问题,但您仍然面临模拟 Auth.getCurrentUser 以测试运行的问题隔离阻断。您引用的博客文章是正确的,因为您应该寻求利用模块的配置阶段来 stub /监视运行阶段使用的依赖项。因此,在您的测试中:

module('mean-starter', function ($provide) {
  $provide.value('Auth', {
    getCurrentUser: jasmine.createSpy()
  });
});

关于您的最后一个问题,您可以通过将它们声明为模块来创建可重用的模拟。例如,如果您想为 Auth 创建一个可重用的模拟工厂,您可以在单元测试之前加载的单独文件中定义它:

angular.module('mock-auth', [])

 .factory('Auth', function() {
   return {
     getCurrentUser: jasmine.createSpy()
   };
 });

然后将它加载到您需要它的任何模块之后的测试中,因为 Angular 将覆盖具有相同名称的任何服务:

module('mean-starter', 'mock-auth');

关于javascript - 在 Angular 单元测试中应该如何处理运行 block ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32169240/

有关javascript - 在 Angular 单元测试中应该如何处理运行 block ?的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

  3. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  4. ruby - Ruby 的 Hash 在比较键时使用哪种相等性测试? - 2

    我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。

  5. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  6. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

    Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

  7. ruby-on-rails - Enumerator.new 如何处理已通过的 block ? - 2

    我在理解Enumerator.new方法的工作原理时遇到了一些困难。假设文档中的示例:fib=Enumerator.newdo|y|a=b=1loopdoy[1,1,2,3,5,8,13,21,34,55]循环中断条件在哪里,它如何知道循环应该迭代多少次(因为它没有任何明确的中断条件并且看起来像无限循环)? 最佳答案 Enumerator使用Fibers在内部。您的示例等效于:require'fiber'fiber=Fiber.newdoa=b=1loopdoFiber.yieldaa,b=b,a+bendend10.times.m

  8. ruby-on-rails - 迷你测试错误 : "NameError: uninitialized constant" - 2

    我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test

  9. ruby - 即使失败也继续进行多主机测试 - 2

    我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r

  10. ruby-on-rails - 如何使辅助方法在 Rails 集成测试中可用? - 2

    我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel

随机推荐