Template-Url Directive Unit Testing without Karma - angularjs

SOLUTION = Solution Plunker
I have tried manually passing the template in testing - is it even good way of doing it ? How to make it passes !!!!!!!
How to write unit test for Simple directive with template-Url without Using Karma . I have tried following after seeing examples on stack-overflow but no success.
Directive
app.directive("crazy", function () {
return {
restrict: "A",
templateUrl:"directivetemplate.html"
};
});
Spec
describe('Directive: crazy', function () {
beforeEach(module('plunker'));
beforeEach(inject(function($templateCache) {
var directiveTemplate = null;
var req = new XMLHttpRequest();
req.onload = function() {
directiveTemplate = this.responseText;
};
req.open("get", "directivetemplate.html", false);
req.send();
$templateCache.put("directiveTemplate.html", directiveTemplate);
}));
it('should exist', inject(function ($rootScope, $compile) {
element = angular.element('<div crazy></div>');
element = $compile(element)($rootScope);
$rootScope.$apply();
expect(element.children().length.toBe(2));
}));
});

Related

How to test an Image onload function within an $observe in karma

I have the following directive which tells me whether or not the image i'm trying to use has loaded successfully or not:
return {
restrict: 'A',
scope: {
imageLoad: '#'
},
link: function(scope, element, attrs) {
attrs.$observe('imageLoad', function (url) {
var deferred = $q.defer(),
image = new Image();
image.onerror = function () {
deferred.resolve(false);
};
image.onload = function () {
deferred.resolve(true);
};
image.src = url;
return deferred.promise;
});
}
};
All i then want to do is two simple tests that test image.onerror and image.onload but i only seem to get into the on error function, here's what i have so far:
compileDirective = function() {
var element = angular.element('<div data-image-load="http://placehold.it/350x150"></div>');
$compile(element)(scope);
$rootScope.$digest();
return element;
};
beforeEach(inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
scope = $rootScope.$new();
}));
it('should do something', function() {
var compiledElement, isolatedScope;
compiledElement = compileDirective();
isolatedScope = compiledElement.isolateScope();
expect(true).toBe(true);
});
obviously this test passes as it just expects true to be true, however in terms of coverage this gets into the onerror function, so i somehow need to test that the deferred.promise should return false.
so ultimately a two part question, how do i get the result of the deferred.resolve?
and secondly how do i get into the onload function?
i've had a look around and seen some suggestions of adding the following:
element[0].setAttribute('imageLoad','http://placehold.it/350x150');
$compile(element)(scope);
element.trigger('imageLoad');
and leaving the data-image-load="" blank, but haven't seemed to have any luck, any suggestions would be great.
From what you have shown, you shouldn't need the promise at all.
Even if you did, since the promise is only used internally and no one is using the result, it should be considered an implementation detail and your test shouldn't care about it.
Let us say you have the following directive:
app.directive('imageLoad', function() {
return {
restrict: 'A',
scope: {
imageLoad: '#'
},
link: function(scope, element, attrs) {
var fallback = 'http://placekitten.com/g/200/300';
attrs.$observe('imageLoad', function(url) {
var image = new Image();
image.onerror = function(e) {
setBackground(fallback);
};
image.onload = function() {
setBackground(url);
};
image.src = url;
});
function setBackground(url) {
element.css({
'background': 'url(' + url + ') repeat 0 0'
});
}
}
};
});
Demo of it in use: http://plnkr.co/edit/3B8t0ivDbqOWU2YxgrlB?p=preview
From an outside perspective, the purpose of the directive is to set the element's background to either the passed url or to the fallback, based on if the passed url is working.
So what you want to test is:
The passed url is working - should use passed url as background.
The passed url is not working - should use fallback as background.
This means that you need to be able to control if the image can be loaded or not.
To prevent any network traffic in your test I would recommend using data URIs instead of URLs.
Example:
var validImage = 'data:image/jpeg;base64, + (Valid data omitted)';
var invalidImage = 'data:image/jpeg;base64,';
Full example:
describe('myApp', function() {
var $compile,
$rootScope;
beforeEach(module('myApp'));
beforeEach(inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
var validImage = 'data:image/jpeg;base64, + (Valid data omitted)';
var invalidImage = 'data:image/jpeg;base64,';
compileDirective = function(url) {
var element = angular.element('<div data-image-load="' + url + '"></div>');
return $compile(element)($rootScope.$new());
};
it('should use correct background when image exists', function(done) {
var element = compileDirective(validImage);
$rootScope.$digest();
setTimeout(function() {
var background = element.css('background');
expect(background).toBe('url("' + validImage + '") 0px 0px repeat');
done();
}, 100);
});
it('should use fallback background when image does not exist', function(done) {
var element = compileDirective(invalidImage);
$rootScope.$digest();
setTimeout(function() {
var background = element.css('background');
expect(background).toBe('url("http://placekitten.com/g/200/300") 0px 0px repeat');
done();
}, 100);
});
});
Note that since loading of an image is asynchronous you need to add a bit of waiting in your tests.
You can read more about how it is done with Jasmine here.
Demo: http://plnkr.co/edit/W2bvHih2PbkHFhhDNjrG?p=preview

How to increase the coverage on testing angular directive?

So here is my angular directive. Simple one that uses a template url
angular.module('my.directives')
.directive('userNameDisplay', function() {
return {
restrict: 'E',
scope: {
user: '=user',
status: '=status'
},
templateUrl: '/partials/userNameDisplay.html'
};
});
The spec is as follows. Again it tries to cover all cases.
describe('user-name-display', function () {
var elm, scope;
beforeEach(module('my.directives', '/partials/userNameDisplay.html'));
beforeEach(inject(function ($compile, $rootScope) {
scope = $rootScope;
elm = angular.element('<user-name-display user="someUser" status="status"></user-name-display>');
$compile(elm)(scope);
}));
it('should have the correct isolate scope values', function () {
scope.someUser = {
name: "John",
color: "blue"
};
scope.status = true;
scope.$digest();
var isoScope = elm.isolateScope();
expect(isoScope.user.name).toBe('John');
expect(isoScope.displayCollaboratorStatus).toBe(true);
});
it('should render html within the partial accordingly', function () {
scope.someUser = {
name: "John"
};
scope.status = false;
scope.$digest();
var cBoxSpan = elm.find("span.user-collaborator-box");
expect(cBoxSpan.length).toBe(0);
var userNameBox = elm.find("span.user-name");
expect(userNameBox[0].innerHTML).toBe("John");
});
});
The coverage report looks like the one below. I am using Karma (which uses Istanbul) to get the code coverage. I am trying to increase it to 100%. I can't figure out from the report what I am missing. It says return statement was never hit, but without it, the isolate bindings will not take place. How can I get the coverage to go 100%?
Here is the image of the report
http://imgur.com/NRzTjyZ
I don't think you'll get coverage from a beforeEach block.
Try adding this test (it's identical to your beforeEach code):
it('should compile', function() {
scope = $rootScope;
elm = angular.element('<user-name-display user="someUser" status="status"></user-name-display>');
$compile(elm)(scope);
});

AngularJS changing controller mocks

I'm using this construct:
Directive with a ControllerAs.
The Controller has a depencency on a Service which does REST requests.
The directive and the controller:
angular.module('app')
.directive('thingsList', function () {
return {
templateUrl: 'thingsListEntry-template.html',
restrict: 'A',
controller: 'thingsListController as ctrl'
};
})
.controller('thingsListController', function (thingsStorage) {
thingsStorage.getList().then(angular.bind(this, function (response) {
this.things = response;
}));
});
What I want to do now is to test the directive with a controller mock:
describe('things list test suite', function() {
describe('tests for the directive', function () {
var scope, render, element, mockController;
/**
* Mock the controller
*/
beforeEach(module('app', function ($provide, $controllerProvider) {
$controllerProvider.register('thingsListController', function () {
this.things = [];
mockController = this;
});
}));
beforeEach(inject(function($rootScope, $compile) {
scope = $rootScope.$new();
var angularElement = angular.element('<div things-list></div>');
var compileFunction = $compile(angularElement);
render = function () {
element = compileFunction(scope);
$rootScope.$digest();
};
}));
it('should be empty without things', function() {
render();
expect(element[0].querySelectorAll('div.things-list-entry').length).toEqual(0);
});
What I would like to do next is to change the things in the controller mock and test that. I don't know how to do that
it('should contain 1 entry with 1 thing', function () {
mockController.things = [{'name':'1'}];
render();
expect(element[0].querySelectorAll('div.thing-list-entry').length).toEqual(1);
});
Here I'm setting mockController.things, but I'm not sure how to get to the mockController. The version above sets it in the mock setup. I also tried using scope.ctrl.things and couple other things but nothing works. Any suggestions?
Try scope.mockController.things instead of mockController.things.

Mocking and testing an Angular resource and function in a directive test using Jasmine

I'm going through the process of refactoring my controller function into more streamlined ones in my directives.
Am reasonably new to Angular and am running into problems mocking and testing my promises within the directives.
Within the function, I call a Box.reboot() from the directive rebootBox.
app.directive("rebootBox", ["Box", function(Box) {
return {
restrict: "A",
link: function( scope, element, attrs ) {
element.bind( "click", function() {
Box.reboot({id: scope.box.slug}).$promise.then(function(results) {
scope.box.state = 'rebooting';
}, function(errors) {
scope.box.errors = true;
})
});
}
}
}])
My tests pass in the controller specs because I am able to do something like this:
fakeFactory = {
reboot: function () {
deferred = q.defer();
return {$promise: deferred.promise};
}
...
}
MainCtrl = $controller('MainCtrl', {
$scope: scope,
Box: fakeFactory,
});
However, I can't get my head around how I am supposed to do this in my directive test?
I've tried this but I don't understand how I can mock what I did in the controller, ie:
Box: fakeFactory
My directive test looks like this so far:
describe('box reboot', function () {
var $scope,
element,
deferred,
q,
boxFactory;
beforeEach(module('myApp'));
beforeEach(inject(function($compile, $rootScope, $q) {
$scope = $rootScope;
q = $q;
element = angular.element("<div reboot-box></div>");
$compile(element)($rootScope)
boxFactory = {
reboot: function () {
deferred = q.defer();
return {$promise: deferred.promise};
}
};
}))
it("should reboot a box", function() {
spyOn(boxFactory, 'reboot').andCallThrough()
$scope.box = {}
element.click();
deferred.resolve({slug: 123});
$scope.$apply()
expect(boxFactory.reboot).toHaveBeenCalled();
});
...
Obvs. it fails because I'm spying on boxFactory.
What is the best way to go about testing such a function?
--- EDIT ----
Further to the comment below, I've used $provide to mock the service call:
beforeEach(module('myApp', function($provide) {
boxFactory = {
get: function () {
deferred = q.defer();
return {$promise: deferred.promise};
},
reboot: function () {
deferred = q.defer();
return {$promise: deferred.promise};
},
};
$provide.value("Box", boxFactory);
I can now call deferred.resolve successfully and all my tests pass bar one.
expect(boxFactory.reboot).toHaveBeenCalled();
Is there a specific reason why this fails and how can I get it to pass?

AngularJS, Unit testing a directive that uses a service that depends on some resources

I am trying to unit test a directive that is using a service that uses some resources. The issue that I have is when I mock the get method of my resource it will be mocked but the callback function will not be called. Therefore, the result won't be what is expected.
I tried to mock the resources by using spyOn as suggested here, and also $httpBackend.when, but neither worked. When I debug the code it will get to the get method but the get callback function never gets called and therefore, the inner callback myCallback that sets my value never gets called.
I am not sure if my approach is even correct, I appreciate your suggestions.
/ Resource
.factory ('AirportTimeZone', function($resource){
return $resource('/api/airport/:airportId/timezone',{airportId: '#airportId'});
})
/ Service that is using my resources:
angular.module('localizationService', [])
.factory('LocalizationService', ['AirportTimeZone','CurrentLocalization',
function (AirportTimeZone,CurrentLocalization) {
function getAirportTimeZone(airport,myCallback){
var options = {}
var localOptions = AirportTimeZone.get({airportId:airport}, function(data){
options.timeZone = data.timeZoneCode
myCallback(options)
});
}
})
/ Directive
.directive('date',function (LocalizationService) {
return function(scope, element, attrs) {
var airTimeZone
function updateAirportTimeZone(_airportTimeZone){
airTimeZone = _airportTimeZone.timeZone
// call other stuff to do here
}
....
LocalizationService.getAirportTimeZone(airport,updateAirportTimeZone)
....
element.text("something");
}
});
/ Test
describe('Testing date directive', function() {
var $scope, $compile;
var $httpBackend,airportTimeZone,currentLocalization
beforeEach(function (){
module('directives');
module('localizationService');
module('resourcesService');
});
beforeEach(inject(function (_$rootScope_, _$compile_,AirportTimeZone,CurrentLocalization) {
$scope = _$rootScope_;
$compile = _$compile_;
airportTimeZone=AirportTimeZone;
currentLocalization = CurrentLocalization;
// spyOn(airportTimeZone, 'get').andCallThrough();
// spyOn(currentLocalization, 'get').andCallThrough();
}));
beforeEach(inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
// $httpBackend.when('GET', '/api/timezone').respond({timeZone:'America/New_York',locale:'us-en'});
// $httpBackend.when('GET', '/api/airport/CMH/timezone').respond({timeZone:'America/New_York'});
}))
describe('Date directive', function () {
var compileButton = function (markup, scope) {
var el = $compile(markup)(scope);
scope.$digest();
return el;
};
it('should',function() {
var html = "<span date tz='airport' format='short' airport='CMH' >'2013-09-29T10:40Z'</span>"
var element = compileButton(html,$scope)
$scope.$digest();
expected = "...."
expect(element.html()).toBe(expected);
});
});
})
As commented above:
After setting up responses with $httpBackend.when, you will still need to call $httpBackend.flush() to flush out the mocked response.
References: http://docs.angularjs.org/api/ngMock.$httpBackend - flushing http requests section

Resources