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);
});
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);
});
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With