I'm creating an element directive that calls a service in its link function:
app.directive('depositList', ['depositService', function (depositService) {
return {
templateUrl: 'depositList.html',
restrict: 'E',
scope: {
status: '#status',
title: '#title'
},
link: function (scope) {
scope.depositsInfo = depositService.getDeposits({
status: scope.status
});
}
};
}]);
The service is trivial for now:
app.factory('depositService', function(){
return {
getDeposits: function(criteria){
return 'you searched for : ' + criteria.status;
}
};
});
I am trying to write a test that ensures that the depositService.getDeposits() is called with the correct status value.
describe('Testing the directive', function() {
beforeEach(module('plunker'));
it('should query for pending deposits', inject(function ($rootScope, $compile, $httpBackend, depositService) {
spyOn(depositService, 'getDeposits').and.callFake(function(criteria){
return 'blah';
});
$httpBackend.when('GET', 'depositList.html')
.respond('<div></div>');
var elementString = '<deposit-list status="pending" title="blah"></deposit-list>';
var element = angular.element(elementString);
var scope = $rootScope.$new();
$compile(element)(scope);
scope.$digest();
var times = depositService.getDeposits.calls.all().length;
expect(times).toBe(1);
}));
});
The test fails because times === 0. This code runs fine in the browser, but in the test the link function and the service never seem to be called. Any thoughts?
plunker: http://plnkr.co/edit/69jK8c
You were missing $httpBackend.flush(), which tells the mock $httpBackend to return a template. The template was never loading, so the directive link function had nothing to link against.
Fixed plunker: http://plnkr.co/edit/ylgRrz?p=preview
code:
describe('Testing the directive', function() {
beforeEach(module('plunker'));
it('should query for pending deposits', inject(function ($rootScope, $compile, $httpBackend, depositService) {
spyOn(depositService, 'getDeposits').and.callFake(function(criteria){
return 'blah';
});
$httpBackend.when('GET', 'depositList.html')
.respond('<div></div>');
var elementString = '<deposit-list status="pending" title="blah"></deposit-list>';
var element = angular.element(elementString);
var scope = $rootScope.$new();
$compile(element)(scope);
scope.$digest();
$httpBackend.flush();
var times = depositService.getDeposits.calls.all().length;
expect(times).toBe(1);
}));
});
Related
I have created a scope method inside my controller which is executing when a button is pressed. I am writing unit test cases for the same. I have injected my module in beforeEach block and created spyon my scope function and then using it in 'it' method and checking whether it is called or not. But getting an error as a method not found.
Controller
'use strict';
angular.module('myApp.view1', ['ngRoute'])
.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/view1', {
templateUrl: 'view1/view1.html',
controller: 'View1Ctrl'
});
}])
.controller('View1Ctrl', ['$scope',View1Ctrl])
function View1Ctrl($scope) {
$scope.user = {
name: '',
last: ''
}
$scope.showFormData = function() {
$scope.formData = $scope.user.name + $scope.user.last;
}
}
spec.js
'use strict';
describe('myApp.view1 module', function () {
var $controller, $rootScope;
beforeEach(module('myApp.view1'));
beforeEach(inject(function (_$controller_, _$rootScope_) {
$controller = _$controller_;
$rootScope = _$rootScope_;
}));
describe('view1 controller', function () {
var $scope, controller, formData;
beforeEach(function () {
$scope = $rootScope.$new();
controller = $controller('View1Ctrl', {
$scope: $scope
});
spyOn(controller, 'showFormData');
});
it('should check for the show form details', function () {
$scope.user.name = "Sandeep";
$scope.user.last = "Gupta";
expect($scope.showFormData).toHaveBeenCalled();
expect($scope.user.name + $scope.user.last).toEqual(firstname);
});
});
});
Need help to resolve this issue.
It looks like you're trying to spy on the showFormData method of the controller:
spyOn(controller, 'showFormData');
However, the showFormData doesn't exist on the controller, it's a method of the controller's scope.
You'll need to use:
spyOn($scope, 'showFormData');
It's also important to know that you need to use the same object to both spyOn and expect(...).toHaveBeenCalled(). In your case you where spying on controller.showFormData(), yet expecting $scope.showFormData() to have been called.
It's a very simple test.. and it's not passing.. If someone can throw some light into this :)
This is the controller code (part of it) that needs to be tested
AppCtrl
$scope.requestAuthorization = function() { requestAuthorization(); };
if ($stateParams.requestAuthorization === true) {
console.log('$stateParams.requestAuthorization');
$scope.requestAuthorization();
}
function requestAuthorization() {
console.log('requestAuthorization()');
// more code here..
}
Test
describe('AppCtrl', function() {
var AppCtrl, $rootScope, $scope, $stateParams;
beforeEach(module('myapp'));
// disable ionic cache to avoid GET errors
beforeEach(module(function($provide, $urlRouterProvider) {
$provide.value('$ionicTemplateCache', function() {});
$urlRouterProvider.deferIntercept();
}));
beforeEach(inject(function($controller, _$rootScope_, _$injector_, _$stateParams_) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$stateParams = _$stateParams_;
AppCtrl = $controller('AppCtrl',{
$scope: $scope
});
spyOn($scope, 'requestAuthorization');
$stateParams.requestAuthorization = true;
}));
it('$stateParams.requestAuthorization should be defined', function() {
expect($stateParams.requestAuthorization).toBeDefined();
});
it('$scope.requestAuthorization should be defined', function() {
expect($scope.requestAuthorization).toBeDefined();
});
// this test is not passing..
it('should call requestAuthorization', function() {
expect($scope.requestAuthorization).toHaveBeenCalled();
});
});
The function is actually being called, I can see the console.log in the console, but it's not passing.
Easy tests, all passing.. except the last one..
Thanks for your time :)
NOTE: There is a $stateParams.requestAuthorization, and a $scope.requestAuthorization. First one is boolean, the other a function, the function is not passing.
In your beforeEach block, define the $stateParams before instanciate the Controller.
beforeEach(inject(function($controller, _$rootScope_, _$injector_, _$stateParams_) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$stateParams = _$stateParams_;
$stateParams.requestAuthorization = true;
AppCtrl = $controller('AppCtrl',{
$scope: $scope,
$stateParams: $stateParams
});
spyOn($scope, 'requestAuthorization');
}));
I am trying to get 100% test coverage for a directive. The directive has a controller with a function that uses the window.confirm method.
'use strict';
(function() {
angular
.module('app')
.directive('buttonToggle', buttonToggle);
function buttonToggle() {
var buttonToggleController = ['$scope', function($scope) {
$scope.toggle = function() {
var confirmResponse = (window.confirm('Are you sure?') === true);
if(confirmResponse) {
$scope.on = !$scope.on;
}
return $scope.on;
};
}];
return {
restrict: 'E',
templateUrl: 'client/modules/buttonToggle/buttonToggle.html',
replace: true,
scope: {
on: '='
},
controller: buttonToggleController
};
}
})();
I have tested to make sure that everything is defined, but I am not able to enter the if statement in the controller's $scope.toggle method.
describe('The buttonToggle directive', function() {
var $compile,
$scope,
btElement = '<button-toggle></button-toggle>',
compiledElement,
window,
confirm,
btElementPath = 'client/modules/buttonToggle/buttonToggle.html',
btController;
beforeEach(module('app'));
beforeEach(module(btElementPath));
beforeEach(inject(function(_$compile_, _$rootScope_, $templateCache, $window) {
$compile = _$compile_;
window = $window;
spyOn(window, 'confirm');
$scope = _$rootScope_.$new();
var template = $templateCache.get(btElementPath);
$templateCache.put(btElementPath, template);
var element = angular.element(btElement);
compiledElement = $compile(element)($scope);
$scope.$digest();
btController = element.controller('buttonToggle', {
$window: window
});
scope = element.isolateScope() || element.scope();
}));
it('should be defined', function() {
expect(compiledElement.html()).toContain('btn');
});
describe('buttonToggle controller', function() {
it('should be defined', function() {
expect(btController).not.toBeNull();
expect(btController).toBeDefined();
});
describe('toggle', function() {
it('should be defined', function() {
expect(scope.toggle).toBeDefined();
});
it('should confirm the confirmation dialog', function() {
scope.toggle();
expect(window.confirm).toHaveBeenCalled();
});
});
});
});
I am guessing it has something to do with mocking the $window service, but I'm not sure if I will be able to test that since it isn't declared globally. So, is the controller's function fully "unit testable"? If not, should I write the directive's controller in a separate file and use angular.module.controller? If yes, then how am I able to test it, or what am I missing?
Use angular's $window service instead of window directly, which is what you are doing in your test but not in your directive.
Then you can mock any of its functions:
spyOn($window, 'confirm').and.returnValue(false);
I have a directive that is depended on a controller which gets data from an api though Ajax call. It works correctly. I am trying to test it using jasmine and the strange issue is that when I debug the code and check for a value of let's say $scope.measurement it returns true, but when I run in the terminal it can't find $scope.measurement and raises an error Expected undefined to be true. no clue what can be the issue. I thought the problem might be with an isolated scope, but the element doesn't have a function isolateScope(). Is there any idea what can be the problem?
Here is the controller:
'use strict';
angular.module('myApp')
.controller('MeasurementsTimelineCtrl', ['$scope', 'Measurements', function($scope, Measurements) {
$scope.measurements = null;
var userId = $scope.currentUser ? $scope.currentUser.id : null;
if (userId) {
var listOfMeasurements = Measurements.users(userId);
listOfMeasurements.then(function(data){
$scope.measurements = data;
});
}
});
This is the directive:
'use strict';
angular.module('myApp')
.directive('measurementTimeline', function() {
return {
restrict: 'E',
templateUrl: 'myView.html',
controller: 'MeasurementsTimelineCtrl',
link: function(scope, element){
scope.$on('measurements-updated', function(measurements) {
_.defer(function(){
if(measurements) {
scope.measurementScroll = true;
}
});
});
}
};
}]);
And this is the test:
'use strict';
describe('Directive: measurementTimeline', function () {
var $rootScope, $compile, element, scope, createController, $httpBackend, apiUrl;
beforeEach(function() {
module('myApp');
inject(function($injector) {
$rootScope = $injector.get('$rootScope');
$compile = $injector.get('$compile');
$httpBackend = $injector.get('$httpBackend');
apiUrl = $injector.get('apiUrl');
});
scope = $rootScope.$new();
element = angular.element('<dashboard-measurement-timeline></dashboard-measurement-timeline>');
element = $compile(element)(scope);
scope.$digest();
scope.measurements = [{id: 'someId', time_of_test: 'Tue, 30 Dec 2014 14:00:00 -0000'},
{id: 'someId', time_of_test: 'Fri, 13 Jun 2014 14:00:00 -0000'}];
scope.$broadcast('measurements-updated', scope.measurements);
scope.$apply();
});
describe('PUser', function(){
beforeEach(function(){
scope.currentUser = null;
});
it('should ......', function () {
expect(scope.measurementScroll).toBe(true);
});
});
});
how about ?
it('should ......', function () {
expect(element.scope().measurementScroll).toBe(true);
});
UPDATE:
and I think you also need to use the andCallThrough method on the _.defer
spyOn(obj, 'method').andCallThrough()
Edit
Seems that the bellow solution is a "Perfect wrong case". However the tests passes they never fall, even if they are wrong.
Wrong solution
The test passed after altering the following:
it('should ......', function () {
scope.$evalAsync(function() {
expect(scope.measurementScroll).toBe(true);
});
});
Update: Right Solution
The right solution for this problem was solved by #Michal Charemza in this question -
How to test _.defer() using Jasmine, AngularJs
I am trying to write a unit test to test that tests a factory that performs a http.get and then tests the scope bindings.
The factory is called within my controller.
Here's a plunker showing my http.get: http://plnkr.co/edit/VqUSeTiEj3MP37tAXKad?p=preview
Ctrl:
app.controller('MainCtrl', function($scope, $http, factoryGetJSONFile) {
$scope.name = 'World';
factoryGetJSONFile.getMyData(function(data) {
$scope.Addresses = data.Addresses.AddressList;
$scope.People = data.Names.People;
$scope.Country = data.Country;
});
});
Test:
describe('with httpBackend', function () {
var app;
beforeEach(function () {
app = angular.mock.module('plunker')
});
describe('MyCtrl', function () {
var scope, ctrl, theService, httpMock;
beforeEach(inject(function ($controller, $rootScope, factoryGetJSONFile, $httpBackend) {
httpMock = $httpBackend;
scope = $rootScope.$new();
ctrl = $controller('MyCtrl', {
$scope: scope,
factoryGetJSONFile: factoryGetJSONFile,
$httpBackend: httpMock
});
}));
it("should make a GET call to data.json", function () {
console.log("********** SERVICE ***********");
httpMock.expectGET("data.json?").respond(data);
console.log(data.Addresses);
console.log(data.Names);
console.log(data.Country);
//expect(factoryGetJSONFile.getMyData()).toBeDefined();
httpMock.flush();
});
})
});
The test for the http.get seems ok, but when i try logging the reponse (data), an error occurs.
UPDATE:
When i try to log the call via:
console.log(httpMock.expectGET("data.json?").respond(data));
Undefined is displayed.