I am doing unit testing with the Karma, Mocha, Chai and CoffeeScript stack, and looking to test for a variable being an angular scope.
Something like this would be nice but does not work:
scope = $rootScope.$new()
expect(scope).to.be.an.instanceOf $rootScope
I've already tried all the variants I can think of, includeing $rootScope:: and $rootScope.$new() as arguments for instanceOf.
Is there a way to do this?
Currently I am going with this:
expect(scope.$id).to.exist
which is less than ideal.
ANSWER:
here is "null"s answer in coffee / mocha / chai
expect(scope.constructor.name).to.equal 'Scope'
Not quite sure what you're trying to accomplish but this will be true in a jasmine test:
var $scope = $rootScope.$new();
expect($scope.constructor.name).toBe('Scope');
If you have access to $rootScope then you can do something like this:
var $scope = $rootScope.$new();
expect($scope.constructor).toEqual($rootScope.constructor);
This also works just fine with minified versions of angular.
If something is not a Scope object then you can convert it to one like this:
// In this case `context` might be a scope object or just a POJO.
if (context.constructor !== $rootScope.constructor) {
context = angular.extend($rootScope.$new(), context);
}
Related
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
scope
ngMock $controller
ng $controller
I'm having trouble understanding how to set up Jasmine to work with Angular so I can do testing. I'm following the instructions here under the heading titled "Testing a controller". According to the documentation, you should have your app & controller, defined like you normally would (this is pasted from the documenation):
angular.module('app', [])
.controller('PasswordController', function PasswordController($scope) {
//controller code goes here (removed for brevity)
});
and then you should have as your testing suite code, for example (pasted from the documentation as well).
describe('PasswordController', function() {
beforeEach(module('app'));
var $controller;
beforeEach(inject(function(_$controller_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
}));
describe('$scope.grade', function() {
it('sets the strength to "strong" if the password length is >8 chars', function() {
var $scope = {};
var controller = $controller('PasswordController', { $scope: $scope });
$scope.password = 'longerthaneightchars';
$scope.grade();
expect($scope.strength).toEqual('strong');
});
});
});
But I'm terribly confused about a few things.
The documentation explains that you need to use angular-mocks to load in the controller, but in their example, they don't declare ngMocks as an app dependency (see the first block of code I pasted above).
It says that you can use angular.mock.inject to inject the controller into the current context. I loaded in the script http://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-mocks.js and now there is an angular.mock on the global scope, but it does not have an inject method. Furthermore, since the testing code runs outside of the controller, I don't understand how using the ngMocks dependency in the angular app helps with providing global methods for injecting controllers. The whole thing doesn't make sense to me.
Same goes for module. It says you can use it for the beforeEach(module('app'));, and that it's provide by angular-mocks, but angular.mock has no module method.
If someone could explain what I'm doing wrong I would very much appreciate it!
So I discovered that problem was that my script tag for angular-mocks was before my script tags for Jasmine when it really needs to go after. In the typical spirit of Angular "documentation", this was mentioned nowhere. After rearranging the script tags both module and inject were globally available methods.
So to answer my first question, you don't need to put ngMock in the dependencies. This answers questions 2 and 3 since module and inject are both now globally available.
So the scripts need to be placed in this order.
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.4/jasmine.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.4/jasmine.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.4/jasmine-html.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.3.4/boot.js"></script>
<!--angluar mocks script MUST go after the other declarations otherwise it won't add the inject and module methods to the scope -->
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular-mocks.js"></script>
Is there a way to provide a non-Angular injection target to the Angular $injector such that Angular constructs like $http, $scope, $location or $q can be injected into it?
//non-angular injection container
var injectionTarget= {
$http:undefined,
$scope:undefined
}
//means to inject into target - this is the part in question
var injector = angular.injector();
injector.injectInto( injectionTarget, ["$http", "$scope"]);
I'm having the hardest time finding any info on how to accomplish what I would assume is a very sought-after feature.
I think that probably the easiest way to do this would be to register your objects as services with the module.
var myObject = {} //Defined elsewhere or here as empty
app.service(‘aReferenceName’, function($http){
myObject.$http = $http;
return myObject;
});
This would have the double effect of setting the properties you want on your object, and making it accessible from angular as needed. It's also a pretty simple block of code. Note the implication though that as a service it would be a singleton from angular's perspective. If you need to do it as a class with many instances, you'll be wanting a factory.
I am currently working on a tutorial that integrates angular JS into a Rails app.
Tests are setup as follows:
describe( 'Club functionality', function() {
// mock Application to allow us to inject our own dependencies
beforeEach(angular.mock.module('league'));
// create the custom mocks on the root scope
beforeEach(angular.mock.inject(function($rootScope, _$httpBackend_, $state){
//create an empty scope
scope = $rootScope.$new();
// we're just declaring the httpBackend here, we're not setting up expectations or when's - they change on each test
scope.httpBackend = _$httpBackend_;
scope.$state = $state;
}));
afterEach(function() {
scope.httpBackend.verifyNoOutstandingExpectation();
scope.httpBackend.verifyNoOutstandingRequest();
});
...
After finishing that section of the tutorial and browsing some of the Angular docs it is still not clear to me why the underscores are used when including the $httpBackend dependency. Why is this mocked out like so? scope.httpBackend = _$httpBackend_;
This one is more simple than it looks.
For convenience, we want to reference our services / scopes across our test suite as we are used to in our applications. So we need to save their reference at the outer function scope.
First we need to inject them so we try to do so without underscores like so:
var $httpBackend;
beforeEach(angular.mock.inject(function( $httpBackend ){
The problem is that the inner function scope variable $httpBackend shadows the outer function scope variable $httpBackend, so we cannot go up the scope chain to set our reference outside.
To fix it, we must have different names for the inner and the outer scope variables. The underscores are just a little help from the $injector to do it without pain.
DISCLAIMER: I did not write this answer. It was copied from here.
So the key to the "secret" is here:
https://github.com/angular/angular.js/blob/master/src/auto/injector.js#L57
Basically $injector will strip leading / trailing underscores when
inspecting function's arguments (to retrieve dependencies).
This is useful trick since we can do $rootScope = _$rootScope_; and
then, later in the tests use $rootScope. It kind of looks nicer since
test code uses the same variables as a controller would use. At
somehow having leading $ helps to remember that those are injected
variables.
Of course we could do: rootScope = $rootScope; but it wouldn't be so
obvious that rootScope was injected.
Is there any difference/preference between:
inject(function($injector) {
rootScope = $injector.get('$rootScope');
});
And
inject(function($rootScope){
rootScope = $rootScope;
});
Are the eqal as far as getting a resource injected into a test in Jasmine?
From the documentation on the inject function:
The inject function wraps a function into an injectable function. The
inject() creates new instance of $injector per test, which is then
used for resolving references.
So, to answer your question, no, there really isn't a difference in the two ways, other than (in my opinion) it's a lot easier to just use the inject function to get dependencies instead of going through the $injector