This is a continuation of another question I asked that was successfully answered.
I'm learning how to unit test AngularJS applications with Karma, Jasmine, and ngMock. Here's the code I have a question about:
describe('myController function', function() {
describe('myController', function() {
var scope;
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $controller) {
// These are the 2 lines I'm a bit confused about:
scope = $rootScope.$new();
$controller('MyController', {$scope: scope});
}));
it("...");
});
});
Question 1: Why do we create a new scope
and include it in the locals injection area in this line: $controller('MyController', {$scope: scope});
? It seems to work just fine (as in, scope
now represents the $scope
object from that controller and has all the properties and functions it's supposed to), but the code seems to imply that we're resetting the controller's $scope
with our newly-created (and empty) scope
(from the line scope = $rootScope.$new();
). So I think I'm just not fully understanding the purpose/inner-workings of those locals.
Question 2: I also have seen from my searching that new scope
gets created in 2 common ways, either the way shown above with $rootScope.$new()
, or by simply declaring scope = {}
. Even the Angular docs do it both ways, as seen here (using $rootScope.$new()
) and here (using $scope = {}
. Why do this one way over another? Is there a difference?
Why do we create a new scope
There are a couple of reasons that come to mind, but the short answer is that you don't have to if you don't want to. You can just as easily pass in $rootScope
and your tests will still work. It is also just typically done because it is more in line with what actually happens in angular - the $scope
that the controller is injected with is likely a descendent of $rootScope
.
What is $controller('MyController', {$scope: scope}); doing?
The ngMock
module provides the $controller
function as a sort of constructor for creating your controller - well, technically it's a decorator to the $controller
function in the ng
module, but that's not important. The first argument is typically a string, but can be a function.
If called with a function then it's considered to be the controller constructor function. Otherwise it's considered to be a string which is used to retrieve the controller constructor...
The second argument are the "locals" to be injected into the controller during creation.
So, in your example, you are saying you want to create the "MyController" controller, and you want to inject your scope
variable as the argument named $scope
in the controllers function.
The whole point of this is to inject your own version of the scope
into the controller, but a version that you created in your test, so that you can assert the different things that happen to the scope because of the controller.
This is one of the benefits of dependency injection.
Examples
If the following makes sense:
var scope = {};
scope.add = function(){};
expect(typeof scope.add).toBe('function');
then let's take it one step further and move the adding of the function into another function:
var addFunctionToScope = function(scope) {
scope.add = function(){};
};
var scope = {};
addFunctionToScope(scope);
expect(typeof scope.add).toBe('function');
Now just pretend your new function to add the function to the scope is really just called $controller, instead of "addFunctionToScope".
var scope = {};
$controller('SomeController', {$scope: scope});
expect(typeof scope.add).toBe('function');
Why use $rootScope.$new() over {}
Again, you can use either. However, if you plan on using some of the scope specific functions, like $on
, or $new
, or $watch
, etc - you will want to inject an actual scope object, created using the $new function on an existing scope.
Docs