1

I have a controller that does two things:

1) executes a function when promise is fulfilled ($q.then)

2) executes a function when 'onLocationChangedStart' event is fired

In my unit test, I want to test the functionality of (1). I do this by executing $RootScope.$apply() to trigger the promise resolution. Unfortunately, it turns out that $rootScope.$apply() also emits an 'onLocationChangedStart' event where the url = http://server/ (this is within the angular code).

Obviously that's an issue since it will fire (2) even though that is not what I wanted for this particular unit test.

Is there any simple way around this that does not involve modifying original code just to get the unit test to run? (for example, I could put function (2) on $scope and then mock it to null prior to running the test. But that seems hackish)

Updated with scrubbed code sample:

angular.module('myMod')

.controller("ctrl", function ($scope, $log, myService, $location) {

     myService.doSomething()
           .then(function () {
               //does something

           })

       })


    $scope.$on('$locationChangeStart', routeChange);

    function routeChange(event, currentUrl, nextUrl) {
           //does something
    };

})

//Jasmine unit test
describe('test', function () {
    var $scope,
         ctrl,
         $rootScope;


    beforeEach(function () {

        module("myMod");
          module("myServiceMock");


        inject(function (_$rootScope_, $controller, _myService_) {
            $rootScope = _$rootScope_;
            $scope = $rootScope.$new();


            ctrl = $controller('ctrl', {
                $scope: $scope

            });


        });
    });


    it("Should set startup values appropriately", inject(function () {

        //doing this triggers both the myService promise AND the location event listener
        $rootScope.$apply();



}));
4
  • you should either mock the event triggering function or the promise resolution function. that's the normal way to go for unit tests Commented Nov 13, 2014 at 13:49
  • @Alp - but doesn't that mean I would have to put one of the functions on $scope? Which would mean making something available to the view that it does not need. If true, that's why I consider it "hackish" and didn't want to go down that path. Commented Nov 13, 2014 at 14:05
  • not really, you need to monkey patch the appropriate functionality. i don't know how your application looks, but it's probably something like mocking the $http or $resource service. i cannot give further advice without seeing the code Commented Nov 13, 2014 at 14:08
  • @Alp - OK added code. One simple thing I could do I guess is put the listener detach function on $scope rather than the full function itself. Something like: $scope.onRouteChangeOff = $scope.$on('$locationChangeStart', routeChange); Then call onRouteChangeOff prior to invoking $apply in my unit tests. Still feels wrong though Commented Nov 13, 2014 at 14:25

1 Answer 1

1

You can use $scope.$digest() instead of $scope.$apply(). $digest() will only execute watched expressions on the current scope, whereas $apply() runs $rootScope.$digest():

it("Should set startup values appropriately", function() {
   $scope.$digest();
   // promise resolved
   // onLocationChangedStart event not fired
});
Sign up to request clarification or add additional context in comments.

1 Comment

Nice! Bonus for adding a clear explanation of why it works :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.