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.
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 .
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).
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.
$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.
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.
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.
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