在使用 andCallThrough() 时在每个测试用例之后重置 broadcast()

Reset broadcast() after each test case while using andCallThrough()

我正在使用下面的代码在每个测试用例后重置 $broadcast,但似乎 $rootScope.$broadcast.reset(); 无法正常运行,因为下面的测试应该 return 1,但它 returns 6.

似乎是 andCallThrough() 的原因,因为之前我在没有 andCallThrough() 功能的情况下使用它,但经过一些重构后它给了我一个 TypeError: Cannot read property 'defaultPrevented' of undefined 的错误,所以我有用于防止该错误。

问题是如何在使用 andCallThrough 时重置 broadcast 或者是否有其他更精确的方法?

beforeEach(function() {
   spyOn($http, 'post');
   spyOn($rootScope, '$broadcast').andCallThrough();
});

afterEach(function() {
   $http.post.reset();
   $rootScope.$broadcast.reset();
});

it('should make a POST request to API endpoint', function() {
   $http.post.andCallThrough();
   var response = { id: '123', role: 'employee', email: 'user@email.com', username: 'someUsername' };
   $httpBackend.expectPOST(apiUrl + 'login').respond(response);
   service.login();
   $httpBackend.flush();
   $timeout.flush();
   expect($rootScope.$broadcast.callCount).toBe(1);
   expect($rootScope.$broadcast).toHaveBeenCalledWith(AUTH_EVENTS.loginSuccess, response);
});

一种不同的方法。它是一个类似于 coffeescript 的伪代码。如果某些表达式不清楚,请在评论中提问。应该说这不是您问题的直接答案。只是一种如何以另一种方式做类似事情的方法。

测试更纯净的状态

让我们引入一个变量来处理广播的间谍。我使用 pure spies 是因为 spyOn 在我们处理方法覆盖或混合时,在某些繁琐的情况下可能效果不佳。

rootScope$broadcastSpy = jasmine.createSpy()

我们需要用 stub 交换 origin 实现,它将处理间谍及其状态。通常的存根定义说它是一个没有自己逻辑的实体。所以,我们以纯粹的方式进行,不在这里放置任何逻辑。只有一个标记(以间谍为代表)。

beforeEach module ($provide) ->
  $provide.value '$rootScope',
     $broadcast: rootScope$broadcastSpy

  return

当然,当我们需要重置间谍时,我们需要谈谈茉莉花。

beforeEach ->
  rootScope$broadcastSpy.reset()

让我们定义测试范围,我们将在其中准备所有需要的服务并将它们以声明的方式放入上下文(即 this)。

instance = (fnAsserts) ->
  inject (loginService, $httpBackend, $timeout, apiUrl, AUTH_EVENTS) ->
    fnAsserts.call
      service: loginService
      apiUrl: apiUrl
      AUTH_EVENTS: AUTH_EVENTS
      '$httpBackend': $httpBackend
      '$timeout': $timeout

让我们以黑盒方式编写测试。我们只是设置初始状态,然后开始一个动作,最后让我们检查我们的标记是否有变化。

it 'should make a POST request to API endpoint', instance ->
  # Given
  response = { id: '123', role: 'employee', email: 'user@email.com', username: 'someUsername' }
  @$httpBackend.expectPOST(@apiUrl + 'login').respond(response)

  # When
  @service.login()

  # Then
  @$httpBackend.flush()
  @$timeout.flush()
  expect(rootScope$broadcastSpy.callCount).toBe(1)
  expect(rootScope$broadcastSpy).toHaveBeenCalledWith(@AUTH_EVENTS.loginSuccess, response)

如您所见,我们可以链接实例定义,向它们添加更多信息并以更纯粹的方式编写每个测试,因为我们可以在一个地方看到状态、范围、模拟和其他所需的东西。我们只为标记使用闭包,它保证副作用最小化。

经过长时间调查这种情况下的工作原理,最终通过了测试,目前的解决方案是:

问题不在于重置 broadcast() 或 reset 使用 andCallThrough() 时在每个测试用例之后未调用方法。 问题是 $rootScope.$broadcast.andCallThrough(); 是由其他事件触发的, .callCount() 函数返回 6,这基本上意味着 $broadcast 间谍被调用了 6 次。在我的例子中,我只对 AUTH_EVENTS.loginSuccess 事件感兴趣,并确保它只被广播一次。

expect($rootScope.$broadcast.callCount).toBe(1);
expect($rootScope.$broadcast).toHaveBeenCalledWith(AUTH_EVENTS.loginSuccess, response);

因此挖掘 $rootScope.$broadcast.calls 的方法给了我所有调用的数组,应该从中检索上面两个期望的调用。因此,解决方案是:

it('should make a POST request to API endpoint', function() {
  $http.post.andCallThrough();
  var response = { id: '123', role: 'employee', email: 'user@email.com', username: 'someUsername' };
  $httpBackend.expectPOST(apiUrl + 'login').respond(response);
  service.login();
  $httpBackend.flush();
  $timeout.flush();

  var loginSuccessTriggerCount = _($rootScope.$broadcast.calls)
    .chain()
    .map(function getFirstArgument(call) {
      return call.args[0];
    })
    .filter(function onlyLoginSuccess(eventName) {
      return eventName === AUTH_EVENTS.loginSuccess;
    })
    .value().length;

  expect(loginSuccessTriggerCount).toBe(1);
});

问题的第一部分我到了这里

Reset broadcast()

这对我有用:

$rootScope.$broadcast.calls.reset()