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
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.
I am having some problems testing an AngularJS (1.5) Attribute restricted Directive. See the below example directive and following unit test, of which produces a broken unit test.
Directive
(function (angular) {
'use strict';
function SomethingCtrl($filter) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModelController) {
var exampleFilter = $filter('exampleFilter');
ngModelController.$parsers.push(function(value) {
ngModelController.$setViewValue(value);
ngModelController.$render();
return value;
});
}
};
}
SomethingCtrl.$inject = ['$filter'];
angular.module('something.formatter', [
'filters'
]).directive('somethingFormatter', SomethingCtrl);
}(window.angular));
Directive Unit Test
fdescribe('something.formatter spec', function () {
'use strict';
var scope,
element,
testValue,
compile,
ngModelCtrl;
beforeEach(function () {
module('something.formatter');
compile = function () {
inject([
'$compile',
'$rootScope',
function ($compile, $rootScope) {
scope = $rootScope.$new();
scope.testValue = testValue;
element = angular.element('<input something-formatter ng-model="testValue"');
$compile(element)(scope);
ngModelCtrl = element.controller('ngModel');
scope.$digest();
}
]);
};
});
describe('initialization', function () {
beforeEach(function () {
testValue = 'Yay!';
compile();
ngModelCtrl.$setViewValue('Nay?');
});
it('should be defined', function () {
expect(scope.testValue).toEqual('Nay?');
});
});
});
I tried following these instructions: http://jsfiddle.net/mazan_robert/hdsjbm9n/
To be able to call methods on the ngModelController, like; $setViewValue.
Yet, Jasmine continues to scream at me and tells me that $setViewValue is not a constructor, as well as doesn't even hit console.logs inside the actual directive.
TypeError: undefined is not an object (evaluating 'ngModelCtrl.$setViewValue')
Thoughts?
Thanks so much for your help!
It will work if you close the input tag:
element = angular.element('<input something-formatter ng-model="testValue" />');
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'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);
}));
});
After much reading, it seems that the recommended way to call a web service from an AngularJS controller is to use a factory and return a promise from that.
Here I have a simple factory which calls a sample API.
myApp.factory('MyFactory', ['$http',function($http) {
var people = {
requestPeople: function(x) {
var url = 'js/test.json';
return $http.get(url);
}
};
return people;
}]);
And this is how I call it in the controller
myApp.controller('MyCtrl1', ['$scope', 'MyFactory', function ($scope, MyFactory) {
MyFactory.requestPeople(22).then(function(result) {
$scope.peopleList = result;
});
}]);
While it works fine, I would like to be able to mock the result that is passed in when then is called. Is this possible?
My attempt so far has produced nothing. This is my attempt:
//Fake service
var mockService = {
requestPeople: function () {
return {
then: function () {
return {"one":"three"};
}
}
}
};
//Some setup
beforeEach(module('myApp.controllers'));
var ctrl, scope;
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('MyCtrl1', { $scope: scope, MyFactory: mockService });
}));
//Test
it('Event Types Empty should default to false', inject(function () {
expect(scope.peopleList.one).toBe('three');
}));
The error that I get when running this in karma runner, is
TypeError: 'undefined' is not an object (evaluating 'scope.peopleList.one')
How can I get this test working with my mocked data?
I don't think $httpBackend is what you're after here, you want the whole factory to be mocked without it having a dependency on $http?
Take a look at $q, in particular the code sample under the Testing header. Your issue might be resolved with code that looks like this:
'use strict';
describe('mocking the factory response', function () {
beforeEach(module('myApp.controllers'));
var scope, fakeFactory, controller, q, deferred;
//Prepare the fake factory
beforeEach(function () {
fakeFactory = {
requestPeople: function () {
deferred = q.defer();
// Place the fake return object here
deferred.resolve({ "one": "three" });
return deferred.promise;
}
};
spyOn(fakeFactory, 'requestPeople').andCallThrough();
});
//Inject fake factory into controller
beforeEach(inject(function ($rootScope, $controller, $q) {
scope = $rootScope.$new();
q = $q;
controller = $controller('MyCtrl1', { $scope: scope, MyFactory: fakeFactory });
}));
it('The peopleList object is not defined yet', function () {
// Before $apply is called the promise hasn't resolved
expect(scope.peopleList).not.toBeDefined();
});
it('Applying the scope causes it to be defined', function () {
// This propagates the changes to the models
// This happens itself when you're on a web page, but not in a unit test framework
scope.$apply();
expect(scope.peopleList).toBeDefined();
});
it('Ensure that the method was invoked', function () {
scope.$apply();
expect(fakeFactory.requestPeople).toHaveBeenCalled();
});
it('Check the value returned', function () {
scope.$apply();
expect(scope.peopleList).toBe({ "one": "three" });
});
});
I've added some tests around what $apply does, I didn't know that until I started playing with this!
Gog