Jasmine + AngularJS: How to test $rootScope.$broadcast being called with arguments?

Andrea picture Andrea · Sep 13, 2014 · Viewed 15.8k times · Source

I am trying to write a unit test that verifies that $rootScope.$broadcast('myApiPlay', { action : 'play' }); is called.

Here is the myapi.js

angular.module('myApp').factory('MyApi', function ($rootScope) {
    var api = {};
    api.play = function() {
        $rootScope.$broadcast('myApiPlay', { action : 'play' });
    }
    return api;
});

And here is my Unit Test:

describe('Service: MyApi', function () {

    // load the service's module
    beforeEach(module('myApp'));

    // instantiate service
    var MyApi;
    var rootScope;

    beforeEach(function () {
        inject(function ($rootScope, _MyApi_) {
            MyApi = _MyApi_;
            rootScope = $rootScope.$new();
        })
    });
    it('should broadcast to play', function () {
        spyOn(rootScope, '$broadcast').andCallThrough();
        rootScope.$on('myApiPlay', function (event, data) {
            expect(data.action).toBe('play');
        });
        MyApi.play();
        expect(rootScope.$broadcast).toHaveBeenCalledWith('myApiPlay');
    });
});

Here is the error i'm getting while running grunt test:

PhantomJS 1.9.7 (Windows 7) Service: MyApi should broadcast to pause FAILED
        Expected spy $broadcast to have been called with [ 'myApiPlay' ] but it was never called.

I have also tried with expect(rootScope.$broadcast).toHaveBeenCalled() and I am having a similar error: Expected spy $broadcast to have been called..

I would like to verify that that method has actually been called with the right parameters.

Thank you!

Answer

jcruz picture jcruz · Sep 13, 2014

The reason your tests are not passing is because you are spying on the wrong $broadcast function. In your beforeEach setup, you ask to have the $rootScope injected and then you create a child scope by calling $rootScope.$new().

The return value from $rootScope.$new() is no longer the rootScope but a child of the root scope.

beforeEach(function () {
    //inject $rootScope
    inject(function ($rootScope, _MyApi_) {
        MyApi = _MyApi_;
        //create a new child scope and call it root scope
        rootScope = $rootScope.$new();
        //instead don't create a child scope and keep a reference to the actual rootScope
        rootScope = $rootScope;
    })
});

In your play function, you are calling $broadcast on the $rootScope but in your test you are spying on a child of $rootScope.

$rootScope.$broadcast('myApiPlay', { action : 'play' });

So to wrap it up, remove the call to $rootScope.$new() and just spy on the $rootScope the injector has given you. The $rootScope provided to your unit test is the same $rootScope provided to your API service so you should be spying directly on the $rootScope.

Check out the plunkr http://plnkr.co/edit/wN0m8no2FlKf3BZKjC4k?p=preview