AngularJS + Test Anchor Element Click - angularjs

I'm new to AngularJS. I'm creating some unit tests with Jasmine. I'm trying to understand how to test whether or not a link was clicked. For instance, I have the following written:
it('should click the link', inject(function ($compile, $rootScope) {
var element = $compile('<a href="http://www.google.com" >Google.com</a>')($rootScope);
$rootScope.$digest();
expect(true).toBeTruthy();
}));
Please note, I do NOT want to test if the browser window gets redirected to Google. The reason why is sometimes my links will redirect the user to a url. Sometimes they will trigger some JavaScript. Either way, I'm trying to test whether the link gets clicked or not. Eventually, I will be adding some behavior that ensures the default behavior of a link is skipped. With the idea of test-driven development in mind, I need to test to ensure that link clicks are currently being detected.
How do I test this in AngularJS with Jasmine?
Thank you

This would not be a AngularJS unit/integration test. It would be more like a end to end test.
If you want to write it the "Angular Way" you could write it like this:
In the view:
<p ng-click='redirectToGoogle()'>Google.com</p>
In the controller:
$scope.redirectToGoogle = function(){
$location.path('http://www.google.com');
};
And in your test:
describe('Redirect', function(){
var location, scope, rootScope, controller;
beforeEach(function(){
inject(function ($injector){
rootScope = $injector.get('$rootScope');
scope = rootScope.$new();
controller = $injector.get('$controller')("nameOfYourController", {$scope: scope});
location = $injector.get('$location');
});
});
it('should redirect to google', function){
spyOn(location, 'path');
scope.redirectToGoogle();
expect(location.path).toHaveBeenCalledWith('http://www.google.com');
});
});

Related

Unit Test on $state in function

I have a function that triggers to change page. How can I unit test this? I'm always getting a failed result saying:
Expected ' ' to be 'add'
So the current state name is still the home page
Function
$scope.goToAddVote = function(){
$state.go('add');
}
Unit Testing
it('should redirect index.html to add.html after click on button', inject(function($state) {
scope.goToAddVote();
$state.go('add');
expect($state.current.name).toBe('add');
}));
EDIT: Using Nilo's answer
var mockStateService = {
go: jasmine.createSpy('add')
};
it('should redirect index.html to add.html after click on button', inject(function($state) {
scope.goToAddVote();
$state.go('add');
expect(mockStateService.go).toHaveBeenCalledWith('add');
}));
Your unit test is failing because the state you are trying to recieve is never registered in the first place. Thats why the go function will have no effect which ends in your current state-name being ''
What you should do ist inject a mock of the $state service with a jasmine-spy as a replacement for the go-function. In your assertion you then expect this function to have been called.
This is way cleaner in terms of unit-testing, because you do not also test the capabilities of $state.
The mocked service would look something along the lines of this:
var mockStateService = {
go: jasmine.createSpy()
}
The assertion then should look like this:
expect(mockStateService.go).toHaveBeenCalledWith('add');
EDIT: Your result could also be caused by the fact mentioned by Pankaj Parkar. So you can also try that, but either why you should mock the service so you really only test the code you have written yourself.
EDIT2: You are not injecting the mocked service, but still using the original $state
var mockStateService,
myScope,
ctrl;
beforeEach(inject(function($controller, $rootScope) {
mockStateService = {
go: jasmine.createSpy()
}
myScope = $rootScope.$new();
ctrl = $controller('myController', {
$scope: myScope,
$state: mockStateService
});
}));
// tip: write better descriptions, what you are writing is not what's really happening
it('should redirect index.html to add.html after click on button', function() {
myScope.goToAddVote();
expect(mockStateService.go).toHaveBeenCalledWith('add');
});

Trigger click event on an AngularJS directive in Mocha test suite

I have a regular angular app with a directive. This directive contains an element with a ng-click="clickFunction()" call. All works well when I click that element. I now need to write a test for this click, making sure that this function was actually run when the element was clicked - this is what I'm having trouble with.
Here's a jsfiddle to illustrate my issue: http://jsfiddle.net/miphe/v0ged3vb/
The controller contains a function clickFunction() which should be called on click. The unit test should imitate a click on the directive's element and thus trigger the call to that function.
The clickFunction is mocked with sinonjs so that I can check whether it was called or not. That test fails, meaning there was no click.
What am I doing wrong here?
I've seen the answer to similar questions like Testing JavaScript Click Event with Sinon but I do not want to use full jQuery, and I believe I'm mocking (spying on) the correct function.
Here's the js from the fiddle above (for those who prefer to see it here):
angular.js, angular-mocks.js is loaded as well.
// App
var myApp = angular.module('myApp',[]);
myApp.controller('MyCtrl', function($scope) {
$scope.person = 'Mr';
$scope.clickFunction = function() {
// Some important functionality
};
});
myApp.directive('pers', function() {
return {
restrict: 'E',
template: '<h2 ng-click="clickFunction()" ng-model="person">Person</h2>',
};
});
// Test suite
describe('Pers directive', function() {
var $scope, $controller, template = '<pers></pers>', compiled;
beforeEach(module('myApp'));
beforeEach(inject(function($rootScope, $controller, $compile) {
$scope = $rootScope.$new();
ctrl = $controller('MyCtrl', {$scope: $scope});
compiled = $compile(template)($scope);
// Do I need to run a $scope.$apply() here?
console.log($scope.$apply); // This is a function, apparently.
//$scope.$apply(); // But running it breaks this function.
}));
it('should render directive', function() {
el = compiled.find('h2');
expect(el.length).to.equal(1);
});
it('should run clickFunction() when clicked', function() {
el = compiled.find('h2');
sinon.spy($scope, 'clickFunction');
// Here's the problem! How can I trigger a click?
//el.trigger('click');
//el.triggerHandler('click');
expect($scope.clickFunction.calledOnce).to.be.true
});
});
// Run tests
mocha.run();
Turns out the problem was quite hidden.
Firstly the $scope.$digest and $scope.$apply functions broke the beforeEach function which ultimately led to the whole solution.
Solution
Do not mix angular versions.
In the first fiddle
angular.js version 1.3.0
angular-mocks.js version 1.1.5
In the solved fiddle
angular.js version 1.3.0
angular-mocks.js version 1.3.0
That was the whole problem, and gave me quite obscure errors.
Thanks to Foxandxss from the #AngularJS IRC channel on freenode.
The way to trigger events on the directive with jQlite was simply:
someElement.triggerHandler('click');

How to mock ng-grid when unit testing a controller

I'm currently trying to write tests for existing blocks of code and running into an issue with a controller that has a nested ng-grid inside of it. The issue comes from the controller trying to interact with the grid on initialization.
Testing Software
node#0.10.14
karma#0.10.2
karma-jasmine#0.1.5
karma-chrome-launcher#0.1.2
My Test:
define(["angularjs", "angular-mocks", "jquery",
"js/3.0/report.app",
"js/3.0/report.controller",
"js/3.0/report.columns"
],
function(angular, ngMocks, jquery, oARModule, oARCtrl, ARColumns) {
"use strict";
describe("Report Center Unit Tests", function() {
var oModule;
beforeEach(function() {
oModule = angular.module("advertiser_report");
module("advertiser_report");
});
it("Advertiser Report Module should be registered", function() {
expect(oModule).not.toBeNull();
});
describe("Advertiser Report Controller", function() {
var oCtrl, scope;
beforeEach(inject(function($rootScope, $controller, $compile) {
var el = document.createElement('div');
el.setAttribute('ng-grid','gridOptions');
el.className = 'gridStyle';
scope = $rootScope.$new();
$compile(el)(scope);
oCtrl = $controller('ARController', {
$scope: scope
});
}));
it("Advertiser Report controller should be registered", function() {
expect(oCtrl).not.toBeNull();
});
});
});
});
You'll see where I've tried to create and compile an element with the ng-grid attribute. Without doing this I get the following error:
TypeError: Cannot read property 'columns' of undefined
Which is a result of the controller attempting to call things like
$scope.gridOptions.$gridScope.columns.each
So I added the creation of a div with ng-grid attribute, and got a new error:
TypeError: Cannot set property 'gridDim' of undefined
So, I tried to add scope.gridOptions before the $controller call, but this brought me back to the original error. I've been searching for way to make this work without rewriting the controller and/or templates, since they are currently working correctly in production.
Your (major!) problem here is that the controller is making assumptions about a View. It should not know about and thus not interact with ng-grid. Controllers should be View-independent! That quality (and Dependency Injection) is what makes controllers highly testable. The controller should only change the ViewModel (i.e. its $scope), and in testing you validate that the ViewModel is correct.
Doing otherwise goes against the MVVM paradigm and best practices.
If you feel like you must access the View (i.e. directives, DOM elements, etc...) from the controller, you are likely doing something wrong.
The problem in the second Failing test is gridOptions and myData is not defined prior to the compilation. Notice the sequence of the 2 statements.
Passing
oCtrl = $controller('MainCtrl', { $scope: $scope });
$compile(elm)($scope);
Failing
$compile(elm)($scope);
oCtrl = $controller('MainCtrl', { $scope: $scope });
In both cases you are trying to use the same html
elm = angular.element('<div ng-grid="gridOptions" style="width: 1000px; height: 1000px"></div>');
I suggest you get rid of
oCtrl = $controller('MainCtrl', { $scope: $scope });
maneuvers and use the following HTML element instead
elm = angular.element('<div ng-controller="MainCtrl"
ng-grid="gridOptions" style="width: 1000px; height: 1000px"></div>');
Notice ng-controller="MainCtrl".
So the end story is that you need gridOptions defined somewhere so
that it ngGrid can access it. And make sure gridOptions dependent
code in controller is deferred in a $timeout.
Also take a look at the slight changes in app.js
$timeout(function(){
//your gridOptions dependent code
$scope.gridOptions.$gridScope.columns.each(function(){
return;
});
});
Here is the working plnkr.

AngularJS: testing a service with a persistent rootscope

I want to test my AngularJS alert service like this (using Mocha and Chai):
describe('service', function() {
var alertService;
var $rootScope;
beforeEach(module('components.services'));
beforeEach(inject(function(_alertService_, _$rootScope_) {
alertService = _alertService_;
$rootScope = _$rootScope_;
}));
describe('alertService', function() {
it('should start with zero alerts', function() {
$rootScope.should.have.property('alerts').with.length(0);
});
it('should add an alert of type danger', function() {
alertService.add('danger', 'Test Alert!');
$rootScope.should.have.property('alerts').with.length(1);
});
it('should add an alert of type warning', function() {
alertService.add('warning', 'Test Alert!');
$rootScope.should.have.property('alerts').with.length(2);
});
it('should close via the alert', function() {
var alert = $rootScope.alerts[0];
alert.should.have.property('close');
alert.close();
$rootScope.should.have.property('alerts').with.length(1);
});
});
});
However, the beforeEach method is resetting the rootScope before each test (I kinda expected it to run before each "describe", not each "it"), so counting the number of alerts doesn't work.
What's the best way around this? Have multiple asserts within one big "it"? I'm quite new to unit testing in general and in Javascript in particular so any explanation is very welcome.
it's resetting the rootScope because you have it declared as a variable and not actually injected into the method...try passing in $rootScope and delete the var declaration of it.

$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.

Resources