I have a directive that look like this:
angular.module('myApp', [])
.directive('myDirective', ['$window','MyConfig', function($window,MyConfig){
return {
link: function(scope, element, attr, controller) {
var w = angular.element($window);
function adjustTop(){
var oldtop = scope.top;
if(oldtop<15){
scope.top += 2;
}else{
scope.top = 0;
}
}
if(MyConfig.adjusttop){
w.bind('scroll', function () {
adjustTop();
});
};
}
};
}]);
How can I spyOn to detect adjustTop() function have been called when MyConfig.adjusttop is true?
You shouldn't do that. Testing private members arguably isn't a good idea because it couples your tests to a specific implementation, making them fragile.
In general you should test what your code does, not how it does it. In this case, you could write tests to ensure the scope.top property is correctly updated after the scroll event has been triggered.
Updated to address OP's comment:
Here's a way for you to test what you want:
describe('my test', function() {
var $scope, $window, $compile, MyConfigMock;
beforeEach(function() {
module('plunker');
MyConfigMock = {};
module(function($provide) {
$provide.value('MyConfig', MyConfigMock);
});
inject(function($rootScope, _$window_, _$compile_) {
$scope = $rootScope;
$window = _$window_;
$compile = _$compile_;
});
});
it('updates top on scroll if set to do so', function() {
// Arrange
MyConfigMock.adjusttop = true;
$scope.top = 15;
$compile('<my-directive></my-directive>')($scope);
// Act
$($window).trigger('scroll');
// Assert
expect($scope.top).toBe(0);
});
});
Working Plunker
Related
I am new to unit test.
Please help me to write test case code for following code:
$scope.convertToInt = function (str) {
if (!isNumberEmpty(str) && !isNaN(str)) {
return parseInt(str, 10);
}
return "";
}
I have tried like this, but not able to do.
describe('ConfigurationTestController', function() {
beforeEach(module('usp.configuration'));
describe('ConfigurationController', function () {
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('ConfigurationController', {
'$scope': scope
});
}));
});
});
Please tell me how can I write.......
you need to modify your code a bit. you don't need to use describe twice in your test case.
(function() {
"use strict";
describe("test suite for Configuration test controller", function() {
var scope = null;
beforeEach(module("usp.configuration"));
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
$controller("ConfigurationController", {
$scope: scope
});
}));
it("should convert to int", function() {
expect(scope.convertToInt("2")).to.equal(2);
});
it("should return empty string", function() {
expect(scope.convertToInt("asd")).to.equal("");
});
});
}());
I have a service that adds/removes classes to a couple of HTML elements.
I am trying to test these changes depending on which method is called.
define(['require', 'angular'], function (require, angular) {
'use strict';
var myFactory = function () {
var header = angular.element("#app-header");
var footer = angular.element(document.getElementsByClassName("app-footer"));
var change = false;
return {
red: function() {
header.addClass("alert-warning");
footer.removeClass("notify");
change = true;
},
black: function() {
if (change) {
this.red();
}
}
};
};
return myFactory;
});
I ahve tried:
describe('<-- MyFactory Spec ------>', function () {
var myFactory, $compile, scope;
beforeEach(angular.mock.module('MyApp'));
beforeEach(inject(function(_myFactory_, _$compile_, _$rootScope_){
myFactory = _myFactory_;
$compile = _$compile_;
scope = _$rootScope_.$new();
}));
it('should open the menu', function(){
var header = angular.element("#app-header");
header = $compile(header)(scope);
scope.$digest();
myFactory.red();
scope.$apply();
expect(header).toHaveClass('alert-warning');
expect(change).toBeTruthy();
});
});
With the above, i get error:
TypeError: 'undefined' is not a function (evaluating 'expect(header).toHaveClass('alert-warning')')
I suspect you aren't pulling in jasmine-jquery matchers.
.toHaveClass(...)
Is not a standard Jasmine matcher, you need to add it with jasmine-jquery
I am a Jasmine rookie and trying to figure out how to mock the window.user.username object when testing an angularjs using Jasmine.
var applicationUser = window.user.username;
I just figured out, Not Sure, if this is the right way. But it worked. Hope it helps.
//Main Function
function MyController($scope, $window) {
$scope.HeaderCtrl = function() {
$scope.user = $window.user;
---
}
//Jasmine Code
var $window;
beforeEach(inject(function(_$window_) {
$window = _$window_;
$window.user = 'xyz';
}));
createController = function() {
return $controller('EpicController', {
'$scope': $rootScope,
'$window': $window
});
};
Another way of doing it,
Mocking window in an angularJS test
you have to add user object to jasmine global space.
//Main Function
function MyController($scope, $window) {
$scope.HeaderCtrl = function() {
$scope.user = $window.user;
---
}
//Jasmine Code
var $window;
beforeEach(inject(function(_$window_) {
jasmine.getGlobal().$window = _$window_;
// OR
// here i am assuming that user class has only username property
jasmine.getGlobal().$windows = { "user": {"username": "xyz"}};
$window.user = 'xyz';
}));
createController = function() {
return $controller('EpicController', {
'$scope': $rootScope,
'$window': $window
});
};
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?
How do I check if $emit has been called in an unit test of a directive?
My directive is fairly simple (for the purpose of illustration):
angular.module('plunker', [])
.directive('uiList', [function () {
return {
scope: {
lengthModel: '=uiList',
},
link: function (scope, elm, attrs) {
scope.$watch('lengthModel', function (newVal) {
scope.$emit('yolo');
});
}
}
}])
so every time the attribute uiList changes, it will emit the event.
And I have unit test code as follows:
describe('Testing $emit in directive', function() {
var scope;
var element;
//you need to indicate your module in a test
beforeEach(function () {
module('plunker');
inject(function ($rootScope, $compile) {
scope = $rootScope.$new();
scope.row= 1;
spyOn(scope,'$emit');
element = angular.element('<ul id="rows" ui-list="row">');
$compile(element)(scope);
});
});
it('should emit', function() {
scope.$digest();
scope.row = 2;
scope.$digest();
expect(scope.$emit).toHaveBeenCalledWith("yolo");
});
});
This would always give me error stating that the scope.$emit has never been called.
Is there something wrong with the scope? Can someone please help?
Plunker:http://plnkr.co/edit/AqhPwp?p=preview
Your directive creates an isolated scope, which is calling $emit,so you need to spy on this one ;)
describe('Testing $emit in directive', function() {
var scope;
var element;
var elementScope;
beforeEach(module('plunker'));
beforeEach(inject(function($rootScope, $compile) {
scope = $rootScope.$new();
scope.row = 1;
element = angular.element('<ul id="rows" ui-list="row">');
$compile(element)(scope);
scope.$digest();
elementScope = element.scope();
}));
it('should emit', function() {
// arrange
spyOn(elementScope, '$emit');
// act
scope.$apply(function() {
scope.row = 2;
});
// assert
expect(elementScope.$emit).toHaveBeenCalledWith("yolo");
});
});
Fixed plunker here :)
I'm not sure how did it work for glepetre (not sure that it does actually). For me it didn't. The thing is that the directive runs $new on incomming scope so the scope it creates inside and element.scope() are some thing different. You can check their $id fields. So it does not help to spy on the scope you got from element.scope(). I had smilar problem and had to do a little trick to make it work.
Here comes my test :
beforeEach(inject(function () {
scope = $rootScope.$new();
scope.$apply(function () {
scope.field = null;
scope.name = 'nolength';
});
spyOn(scope, '$new').and.returnValue(scope);
spyOn(scope, '$emit');
var element = angular.element('<my-directive name="name" field="field" title="\'Title\'" ></my-directive>');
noLengthValidation = $compile(element)(scope);
scope.$apply();
elementScope = element.scope();
}));
So the trick was to mock $new function on scope so instead of creating a new scope behind the scene and mess the things up it will return itself! Here comes the test itself :
it('The directive should not react on string length if no max-chars attribute presented', function () {
scope.$apply();
noLengthValidation.isolateScope().field = 'fieldisssssssveeeeerryyyyyylooooooonnnngggggggggggggggggggggggggggg';
scope.$apply();
expect(scope.$emit).toHaveBeenCalledWith('ON_MORE_AWASOME_EVENT', 'nolength');
});
And the the part on the directive that calls $emit. Just in case :
function validate() {
scope.validate = 1;
if (scope.field) {
if (!isValid()) {
scope.$emit('ON_AWASOME_EVENT', {code : scope.name, text : 'Field ' + scope.title + ' is feels bad'});
} else {
scope.$emit('ON_MORE_AWASOME_EVENT', scope.name);
}
}
}