Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing $scope in AngularJs controller with dependency on $filter

I've gone through a few tutorials and basic examples but I'm having a hard time writing unit tests for my controller. I've seen code snippets instantiating controllers and letting angular inject the $rootScope object which in turn is used to create a new scope object for the controller. But I can't figure out why ctrl.$scope? is undefined:

 describe('EmployeeCtrl', function () {
    var scope, ctrl, $httpBackend;

    beforeEach(inject(function (_$httpBackend_, $rootScope, $controller, $filter) {
        $httpBackend = _$httpBackend_;       

        scope = $rootScope.$new();
        ctrl = $controller('EmployeeCtrl', { $scope: scope});
        expect(ctrl).not.toBeUndefined();
        expect(scope).not.toBeUndefined();   //<-- PASS!      
        expect(ctrl.$scope).not.toBeUndefined();  //<-- FAIL!       
    }));
});

I ended up using the scope variable instead of ctrl.$scope but then on my first test I couldn't figure out how to unit test a function variable inside my controller:

Controller:

 function EmployeeCtrl($scope, $http, $filter, Employee) {
  var searchMatch = function (haystack, needle) {
   return false;
  }
 }

Broken unit test:

it('should search ', function () {                
    expect(ctrl.searchMatch('numbers','one')).toBe(false);
});

This is what I get

TypeError: Object # has no method 'searchMatch'

How do you test that function? As a workaround I moved my method to $scope so I could test for scope.searchMatch but I was wondering if this is the only way.

Finally, on my tests is appears $filter is undefined too, how do you inject it? I tried this but didn't work:

ctrl = $controller('EmployeeCtrl', { $scope: scope, $filter: $filter });

Thanks

Update: The method mentioned above to inject $filter works just fine.

like image 432
Ulises Avatar asked Jan 07 '13 19:01

Ulises


People also ask

How do you get the $scope in console?

scope(); $('#elementId'). scope(). $apply(); Another easy way to access a DOM element from the console (as jm mentioned) is to click on it in the 'elements' tab, and it automatically gets stored as $0 .

What is the use of $scope variable in AngularJS?

The $scope in an AngularJS is a built-in object, which contains application data and methods. You can create properties to a $scope object inside a controller function and assign a value or function to it. The $scope is glue between a controller and view (HTML).

How pass data from controller controller to AngularJS?

Approach: To share data between the controllers in AngularJS we have two main cases: Share data between parent and child: Here, the sharing of data can be done simply by using controller inheritance as the scope of a child controller inherits from the scope of the parent controller.

What is difference between $scope and $rootScope object?

$rootscope is available globally (for all Controllers), whereas $scope is available only to the Controller that has created it. Don't get confused by this statement.


2 Answers

My understanding is that, in Angularjs, you pass a scope object to the controller when the application is starting and the controller modify the scope. The controller is not called anymore.

What a controller is really doing, in Angularjs, is initializing the scope: the controller only runs one time. If you understand this, you realize that asking to the controller for the scope like this:

currentScope = myController.scope;

doesn't makes sense.

(Incidentally, one of the things I don't like in Angular is the names that they have choosen. If what a 'controller' is doing is initializing the scope then it's not really a controller. There are a lot of this in the Angular API).

I think that the 'proper' way for testing a 'controller' is creating a new blank scope from scratch in a Jasmine beforeEach clause and using the 'controller' for initializing a new blank scope like this::

var ctrl, myScope;

beforeEach(inject(function($controller, $rootScope) {
    myScope = $rootScope.$new();
    ctrl = $controller('myController', {
        $scope: myScope
    });
}));

And then testing the new created scope has the expected properties:

it('In the scope, the initial value for a=2', function() {
            expect(myScope.a).toBe(2);
});

In other words, you don’t test the controller; you test the scope that the controller has created.

So, you're doing it right.

like image 188
Robert Avatar answered Oct 02 '22 04:10

Robert


Here is another pattern that might be simpler to follow:

var $scope;
beforeEach(module(function ($provide) {
  $scope = {
    // set anything you want here or nothing
  };
  $provide.value('$scope', $scope);
}));

var ctrl;
beforeEach(inject(function ($controller) {
  ctrl = $controller('MyController');
}));

it('should test something...', function () {
  // $scope will have any properties set by
  // the controller so you can now do asserts
  // on them.
});

You can use this same pattern to set values for $location and $window as well as others.

like image 38
Guy Avatar answered Oct 02 '22 03:10

Guy