Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I am using the code bellow to reset $broadcast after each test case, but it seems it $rootScope.$broadcast.reset(); doesn't function properly, since the test bellow should return 1, but it returns 6.

Seems that the reason for this is andCallThrough(), since before I used it without andCallThrough() function, but after some refactor it gave me an error that TypeError: Cannot read property 'defaultPrevented' of undefined, so I had to use to prevent that error.

The question is how can i reset the broadcast while using andCallThrough or is there another more precise approach?

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: '[email protected]', 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);
});
like image 867
Max Avatar asked Jan 27 '15 13:01

Max


2 Answers

After long investigation of how things work in this situation, finally the tests are passed and the solution is the current:

The issue was not about reseting broadcast() or reset method is not called after after each test case while using andCallThrough(). The problem is that the $rootScope.$broadcast.andCallThrough();is triggered by other events and the .callCount() function returned 6, which basically means $broadcast spy was called 6 times. In my case I am interested only in AUTH_EVENTS.loginSuccess event and make sure it was broadcasted only once.

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

Thus digging the method of $rootScope.$broadcast.calls gave my the array of all calls from which the two above expects should be retrieved. Consequently, the solution is:

it('should make a POST request to API endpoint', function() {
  $http.post.andCallThrough();
  var response = { id: '123', role: 'employee', email: '[email protected]', 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);
});
like image 180
Max Avatar answered Nov 15 '22 05:11

Max


A different approach. It is a coffeescript-like pseudo code. Ask in comments if some expressions are not clear. Should say it is not a direct answer on your question. Just a method how to do similar things in a bit another way.

Tests with purer state

Let introduce a variable which will handle a spy for the broadcasting. I use pure spies because spyOn may work bad in some cumbersome cases when we deal with method overriding or mixing.

rootScope$broadcastSpy = jasmine.createSpy()

We need to swap origin implementation with a stub which will handle spies and their states. The usual stub definition says it is an entity which doesn't have its own logic. So, we do it in pure way and don't put any logic here. Only a marker (which is represented by a spy).

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

  return

Sure, we need to talk jasmine when we need to reset a spy.

beforeEach ->
  rootScope$broadcastSpy.reset()

Let's define testing scope where we will prepare all needed services and put them in a declarative way to the context (i.e. this).

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

Let's write the test in blackbox manner. We just setup initial state, then start an action and at the end let's check our marker for changes.

it 'should make a POST request to API endpoint', instance ->
  # Given
  response = { id: '123', role: 'employee', email: '[email protected]', 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)

As you see we can chain instance defitions, add more information to them and write each test in purer way because we see the state, the scope, mocks and other needed stuff in single place. We use a closure only for the marker and it garantees that side effects minimzed.

like image 30
Gulin Serge Avatar answered Nov 15 '22 06:11

Gulin Serge