Angular directive unit testing: $templateCache or $httpBackend? - angularjs

I'm testing directives that use the templateUrl property, and was wondering what the best way is to compile the templates. Is is better to use $templateCache or $httpBackend? I am guessing it's better to use $templateCache since I think this is the use case it was made for, but I've seen it done both ways. Although I haven't got the $httpBackend method fully functional yet.
NOTE: the second test is for the rewrite of the original project. The first test is from the original project.
The $templateCache way:
describe('buttonToggle', function() {
var elm, scope;
beforeEach(module('app'));
beforeEach(module('src/app/partials/buttonToggle/buttonToggle.html'));
beforeEach(inject(function($templateCache, _$compile_, _$rootScope_) {
template = $templateCache.get('src/app/partials/buttonToggle/buttonToggle.html');
$templateCache.put('src/app/partials/buttonToggle/buttonToggle.html', template);
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
it('should have an on/off switch', function() {
var buttonElement = angular.element('<button-toggle></button-toggle>');
var element = $compile(buttonElement)($rootScope);
$rootScope.$digest();
expect(element.text()).toContain('ON');
expect(element.text()).toContain('OFF');
});
});
My nonworking implementation of $httpBackend:
describe('The buttonToggle directive', function() {
var $compile,
$scope,
$httpBackend,
btElement,
btElementPath = 'client/modules/buttonToggle/buttonToggle.html',
btElementFileName = 'buttonToggle.html';
beforeEach(module('app'));
beforeEach(inject(function(_$compile_, _$rootScope_, _$httpBackend_) {
$compile = _$compile_;
$scope = _$rootScope_;
$httpBackend = _$httpBackend_;
$httpBackend.whenGET(btElementPath).respond(200);
$httpBackend.expectGET(btElementFileName).respond(200);
btElement = $compile('<button-toggle></button-toggle>')($scope);
$scope.$digest();
}));
it('should be defined', function() {
expect(btElement.html()).toContain('btn');
});
});
Also, any ideas on how to get the latter test to work? I don't think I setup the whenGET correctly since the error I am getting from my assertion states that the compiled element is empty.

Well I actually found a short answer from the Angular docs. If anyone else has good knowledge on this subject, then please feel free to answer, but this is what I found:
If your directive uses templateUrl, consider using karma-ng-html2js-preprocessor to pre-compile HTML templates and thus avoid having to load them over HTTP during test execution. Otherwise you may run into issues if the test directory hierarchy differs from the application's.

Related

Angular js use of controller in this example

I am reading the Angular JS documentation I am looking at this example:
// testing controller
describe('MyController', function() {
var $httpBackend, $rootScope, createController;
beforeEach(inject(function($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
// backend definition common for all tests
$httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'});
// Get hold of a scope (i.e. the root scope)
$rootScope = $injector.get('$rootScope');
// The $controller service is used to create instances of controllers
var $controller = $injector.get('$controller');
createController = function() {
return $controller('MyController', {'$scope' : $rootScope });
};
}));
My question is what purpose the createController serves, I don't really understand why it is there or what the last line does where $controller is returned or what it has to do with the $scope.
It is the second grey section that contains code underneath the header: Unit testing with mock $httpBackend.
Help would be greatly appreciated.
$controller returns an instance of MyController from the first grey section. To give the controller some context, it passes the $rootScope into the instantiation of the controller. Hence when you execute the controller (as shown in subsequent it() blocks) the controller runs and kicks off the $http.get('/auth.py') request.

Unit Testing AngularJS route with "resolve"

I am trying to unit test one of my routes and I get the infamous "Error: Unexpected request" error. My route takes in a "resolve" parameter and looks like this:
when('/Users',
{
templateUrl: 'app/users/user-list.tpl.html',
controller: 'UserListCtrl',
resolve: {
userAccounts: ['UserAccounts', function(UserAccounts) {
return UserAccounts.query({ id: 123 });
}]
}
})
where UserAccounts is a "resource". I am testing my route as follows:
beforeEach(inject(function (_$route_, _$location_, _$rootScope_, _$httpBackend_) {
$route = _$route_;
$location = _$location_;
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', 'api/Accounts/123/UserAccounts').respond([]);
$httpBackend.when('GET', 'app/users/user-list.tpl.html').respond({});
}));
it('/Users should get user-list template and use UserListCtrl', inject(function($controller) {
$httpBackend.expectGET('app/users/user-list.tpl.html');
$httpBackend.expectGET('api/Accounts/123/UserAccounts');
expect($route.current).toBeUndefined();
$location.path('/Users');
$rootScope.$digest();
expect($route.current.loadedTemplateUrl).toBe('app/users/user-list.tpl.html');
expect($route.current.controller).toBe('UserListCtrl');
}));
And the test fails with Error: Unexpected request: GET http://myserver/api/Accounts/123/UserAccounts. This is the get that my resolve is calling. Any ideas what the right way is to test such a route?
Two things look strange about your test code.
First, your beforeEach method has the GET requests duplicated with those being called in the actual test. Try removing them.
Second, it looks as if you are missing a call to $httpBacken.flush() before your final assertions.
Hopefully this helps.
You can try mocking the service
beforeEach(module(function($provide) {
$provide.value('UserAccounts', {
query: angular.noop
});
}));
If you care whether the service method has been called, you can create a spy.
beforeEach(function() {
spyOn(UserAccounts, 'query');
});
Wow! this was a silly one. I had the complete URL defined in my Resource
http://myserver/api/Accounts/:accountId/UserAccounts/:userId
And I was setting up expectation on my $httpBackend to check for relative paths.
api/Accounts/123/UserAccounts
To unit test resolved values of a route:
var resolvedUserAccounts = $injector.invoke($route.current.$$route.resolve.userAccounts);

$rootScope.digest throwing 'no more request expected' in angular directive unit test

I've coded a directive that checks some permissions and delete an element from the DOM if permissions are KO.
I'd love to unit test it, but... hem, I'm banging my head agains walls to make this simple test work.
I use $rootScope.digest() to compile a piece of html. When calling this function, angular tries to load my app main page and I get the dreaded "no more request expected" error.
So here is the test :
describe('Unit testing permission-needed', function() {
var $compile;
var $rootScope;
var $httpBackend;
// Load the myApp module, which contains the directive
beforeEach(module('app'));
beforeEach(angular.mock.module('ngMockE2E'));
beforeEach(inject(function(_$compile_, _$rootScope_, $injector) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$httpBackend = $injector.get('$httpBackend'); // not sur if I can inject it like others services ?
$httpBackend.whenGET('app/login/login.tpl.html').passThrough(); // this doesn't seem to work
}));
it('should replace the element with the appropriate content', function() {
// Compile a piece of HTML containing the directive
var element = $compile("<div permission-needed><span>Some content goes here</span></div>")($rootScope);
$rootScope.$digest(); // BAM => "no more request expected"
// Do the test here
// expect(....);
});
});
Note that if I use
.respond('some html here');
instead of
.passThrough() it works.
Thank you.
Well, answering myself :
using a $new() rootScope, test is passing :
$rootScope = _$rootScope_.$new();
Hope this help someone.

What is the meaning of underscores on arguments (of the inject function)?

I've been writing tests for some Angular components, using a syntax that I found on google a while ago:
describe('Directive: myDir', function () {
beforeEach(module('myApp'));
beforeEach(module('app/views/my_template.html'));
beforeEach(inject(function ($rootScope, _$compile_, $templateCache) {
$templateCache.put('views/my_template.html', $templateCache.get('app/views/my_template.html'));
var scope, $compile;
scope = $rootScope;
$compile = _$compile_;
element = angular.element("<div my-dir class='my-dir'></div>");
}));
it('does things', function () {
$compile(element)(scope);
scope.$digest();
});
});
My question is specifically about the injection of _$compile_. How is it different from just $compile. Why would I need to do it this way? Why does $compile get redefined, why can't I simply compile with a $compile I inject?
From the Angular official tutorial (Test section):
The injector ignores leading and trailing underscores here (i.e. $httpBackend). This allows us to inject a service but then attach it to a variable with the same name as the service.
In your example, you could rename the variable $compile as, say, compile and then remove the underscores from the parameter name. In fact, you did that to scope so $rootScope remained underscore-free.
Personally I like to keep the name of Angular built-in services in my tests so they can be easily spotted while skimming through the code.

How do I inject dependencies into AngularJS controller tests without using Jasmine-specific inject()

I'm trying to wrap my brain around dependency injection in AngularJS. Let's say this is my very exciting application code:
function PrideRockCtrl($scope, King) {
$scope.king = King;
}
angular.module('Characters', ['ngResource'])
.factory('King', function() {
return "Mufasa";
});
I want to test PrideRockCtrl. If I follow examples in the documentation and in the tutorial, I could use the module('Characters') to configure the injector and use inject() to get some dependencies. i.e.:
describe('Pride Rock', function() {
beforeEach(module('Characters'));
it('should be ruled by Simba', inject(function($rootScope, $controller) {
var scope = $rootScope.$new();
var ctrl = $controller(PrideRockCtrl, {$scope: scope});
expect(scope.king).toEqual("Mufasa");
}));
});
This works fine, but this isn't a cross-test-framework solution. The module() and inject() test helpers are only compatible with Jasmine.
What's the best way to manually accomplish the same dependency injection without using module() or inject()?
I came up with this:
describe('Pride Rock', function() {
it('should be ruled by Mufasa', function() {
var $injector = angular.injector(['Characters']);
var $controller = $injector.get('$controller');
var scope = $injector.get('$rootScope').$new();
var king = $injector.get('King');
var ctrl = $controller(PrideRockCtrl, {$scope: scope, King: king});
expect(scope.king).toEqual("Mufasa");
});
});
This seems very verbose. Is there a better way?
jsFiddle: http://jsfiddle.net/johnlindquist/d63Y3/
The simplest possible version, without using modules would look like this:
describe('Pride Rock', function() {
it('should be ruled by Simba', inject(function($rootScope, $controller) {
var scope = $rootScope.$new();
var ctrl = $controller(PrideRockCtrl, {
$scope: scope,
King:"Mufasa"
});
expect(scope.king).toEqual("Mufasa");
}));
});
In fact you were pretty close with your attempts, the only thing missing was a local dependency in a controller (King:"Mufasa").
In the tests like those, where we do focus on a one, selected class only it is not necessary to use the $injector, as we can manually mock / stub our dependencies. At the end of the day $injector is just giving instances of objects so we can create those our self as well.
Angular controllers are just JS functions. If you don't want anything injected, just call them.
describe('king should be set', function() {
var scope = {};
var lion = "Simba";
PrideController(scope, lion);
expect(scope.king).toEqual(lion)
});

Resources