This is my js file code
$scope.close = function () {
modalInstance.close();
};
unit test case for the js code
beforeEach(inject(function($rootScope) {
var modalInstance = { close: function() {}, dismiss: function() {} };
$controller('BusinessRuleEditCtrl',
{
$scope: scope,
dataFactory: mockdataFactory,
$modal: modal,
modalInstance:modalInstance
});
}));
it(' scope.close () functionality is checked ', function () {
scope.close();
spyOn(modalInstance, 'close').toHaveBeenCalled();
});
when i try to run this spec iam getting error "Cannot read property 'close' of undefined".any suggesion will be appreciated....thanks in advance.
Related
I am trying to test a function that opens a $uibmodal. This is my factory function.
confirmationMessage: function (message) {
var modalInstance = $uibModal.open({
templateUrl: 'views/templates/utilsTemplates/confirmationMessage.html',
backdrop: 'static',
controller: function () {
var messageCtrlVM = this;
// message to show
messageCtrlVM.message = message;
// when yes_button is pressed
messageCtrlVM.yesPress = function () {
modalInstance.close(true);
};
// when no_button is pressed
messageCtrlVM.noPress = function () {
modalInstance.close();
};
},
controllerAs: "messageCtrlVM"
});
return modalInstance.result;
},
In its unit testing file, I am first adding provider for it like so.
beforeEach(angular.mock.module('ui.bootstrap'));
beforeEach(function () {
module(function ($provide) {
$provide.value('$uibModal', function (value) {
return value;
});
});
});
After that I am injecting open, dismiss and close functions with beforeEach like so.
beforeEach(inject(function (_utilsFactory_, _$httpBackend_, _$filter_) {
utilsService = _utilsFactory_;
$httpBackend = _$httpBackend_;
filter = _$filter_;
uibModal = {
open: function () {},
dismiss: function () {},
close: function () {}
};
}));
Finally I am trying to run my unit test by calling factory function.
it('should show a confirmation message', function () {
var spy = spyOn(uibModal, "open").and.callFake(function () {
return {
result: {
then: function () {}
}
};
});
utilsService.confirmationMessage("Are you Sure?");
expect(spy).toHaveBeenCalled();
});
It gives me error that $uibModal.open is not a function.
Your beforeEach should be like this:
beforeEach(module('myApp', function ($provide) {
mockModal = {
result: {
then: function(confirmCallback, cancelCallback) {
this.confirmCallback = confirmCallback;
this.cancelCallback = cancelCallback;
return this;
}
},
opened: {
then: function (confirmCallback, cancelCallback) {
this.confirmCallback = confirmCallback;
this.cancelCallback = cancelCallback;
return this;
}
},
close: function() {
this.opened.confirmCallback(); // covers opened.then success
this.result.confirmCallback(); // covers result.then success
this.result.cancelCallback(); // covers result.then error
},
open: function (object1) {
return this;
}
};
$provide.value('$uibModal', mockModal);
}));
Note that, here, the object which we are providing as $uibModal has the open function. Having this passed in $provide, you would need to callThrough (not callFake after spying)
Feel free to remove result / opened / close if you are not using them here. They are useful when you have corresponding code.
I have a simple enough function that closes an $mdSidenav instance in my application
function closeSideNav() {
$mdSidenav('left').close();
}
I'm now needing to unit test this, but am having trouble writing an expectation for the close() call on $mdSidenav.
I thought about using $provide in my test spec
module(function($provide) {
$provide.value('$mdSidenav', function(id) {
return {
close: jasmine.createSpy('$mdSidenav.close')
}
})
});
beforeEach(inject(function(_$controller_, _$mdSidenav_) {
$controller = _$controller_;
$mdSidenav = _$mdSidenav_;
}));
beforeEach(function() {
vm = $controller('NavbarController', {
$mdSidenav: $mdSidenav
});
});
describe('vm.closeSideNav', function() {
beforeEach(function() {
spyOn($mdSidenav, 'close');
vm.closeSideNav()
});
it('should call $mdSidenav.close()', function() {
expect($mdSidenav.close).toHaveBeenCalled();
});
});
This throws a couple of errors:
Error: close() method does not exist
Error: Expected a spy, but got undefined.
Has anyone managed to mock out $mdSidenav and offer me some guidance please?
Thanks
UPDATE
Based on the suggested answer, I have now updated my test spec to
'use strict';
describe('NavbarController', function() {
var $controller,
vm,
$mdSidenav,
sideNavCloseMock;
beforeEach(function() {
module('app.layout');
sideNavCloseMock = jasmine.createSpy();
module(function($provide) {
$provide.value('$mdSidenav', function() {
return function(sideNavId) {
return {close: sideNavCloseMock}
}
})
});
});
beforeEach(inject(function(_$controller_, _$mdSidenav_) {
$controller = _$controller_;
$mdSidenav = _$mdSidenav_;
}));
beforeEach(function() {
vm = $controller('NavbarController', {
$mdSidenav: $mdSidenav
});
});
describe('vm.closeSideNav', function() {
beforeEach(function() {
vm.closeSideNav()
});
it('should call $mdSidenav.close()', function() {
expect(sideNavCloseMock).toHaveBeenCalled();
});
});
});
And for a sanity check, my actual controller looks as follows:
(function () {
'use strict';
angular
.module('app.layout')
.controller('NavbarController', Controller);
Controller.$inject = ['$mdSidenav'];
function Controller($mdSidenav) {
var vm = this;
vm.closeSideNav = closeSideNav;
//This only affects the sideNav when its not locked into position, so only on small\medium screens
function closeSideNav() {
$mdSidenav('left').close();
}
}
})();
Unfortunately this still isn't working for me, and I end up with a different error
TypeError: undefined is not a constructor (evaluating '$mdSidenav('left').close())
close method doesn't belong to $mdSidenav. $mdSidenav is a function that returns a side nav object. That's why it complains 'close() method does not exist'.
What you can do is mock the $mdSidenav to return an object hat has mocked close method, like this: -
var sideNavCloseMock;
beforeEach(module(function($provide){
sideNavCloseMock = jasmine.createSpy();
$provide.factory('$mdSidenav', function() {
return function(sideNavId){
return {close: sideNavCloseMock};
};
});
}));
then do
it('should call $mdSidenav.close()', function() {
expect(sideNavCloseMock).toHaveBeenCalled();
});
The following test passes whether my expect statement is
expect(propertyFactory.readProperty).should.have.been.called;
or
expect(propertyFactory.readProperty).should.have.not.been.called;
I've tried this with or without the sinon stub.
describe('pf.propertyModule', function () {
var controller;
var propertyFactory;
beforeEach(module('pf.core'));
beforeEach(module('pf.propertyModule'));
beforeEach(inject(function(_propertyFactory_) {
propertyFactory = _propertyFactory_;
/*
sinon.stub(propertyFactory, 'readProperty', function() {
return {
data: {
property: 'xyz'
}
}
});*/
}));
describe('readPropertyCtrl', function () {
it('should have a passing first test', function () {
expect(propertyFactory.readProperty).should.have.been.called;
});
});
});
UPDATE: Ok, so I got past the initial "non-error" by ensuring "sinon-chai" was installed, but now I'm getting the output: thrown: [Function] } is not a spy or a call to a spy!
UPDATE: Ok, so I got the previous error working by calling sinon.spy(). However, now I'm wondering how one would go about mocking a method that is expected to return a promise. I'm getting the error " TypeError: 'undefined' is not an object (evaluating 'propertyFactory.readProperty({id: $stateParams.id}).then')
"
Updated code:
describe('pf.propertyModule', function () {
var ctrl;
var scope;
var propertyFactory;
beforeEach(module('pf.core'));
beforeEach(module('pf.propertyModule'));
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
propertyFactory = {
readProperty: sinon.spy()
};
ctrl = $controller('readPropertyCtrl as vm', {
$scope: scope,
propertyFactory: propertyFactory
});
}));
describe('readPropertyCtrl', function () {
it('should have a passing first test', function () {
expect(scope.vm).to.be.defined;
expect(propertyFactory.readProperty).should.have.been.called;
});
});
});
Code to test:
propertyFactory.readProperty({id: $stateParams.id}).then(getPropertySuccess);
function getPropertySuccess(response) {
vm.loadedProperty = response.data.property;
}
Updated Code:
describe('pf.propertyModule', function () {
var ctrl;
var scope;
var propertyFactory;
beforeEach(module('pf.core'));
beforeEach(module('pf.propertyModule'));
beforeEach(inject(function($rootScope, $controller, $q, _propertyFactory_) {
scope = $rootScope.$new();
propertyFactory = _propertyFactory_;
var deferred = $q.defer();
var promise = deferred.promise;
sinon.stub(propertyFactory, "readProperty").returns(promise);
deferred.resolve({property: 'xyz'});
ctrl = $controller('readPropertyCtrl as vm', {
$scope: scope,
propertyFactory: propertyFactory
});
}));
describe('readPropertyCtrl', function () {
it('should have a passing first test', function () {
expect(scope.vm).to.be.defined;
expect(propertyFactory.readProperty).should.have.been.called;
});
});
});
This attempt is returning thrown: [Function] } is not a spy or a call to a spy!
APP
(function() {
'use strict';
function ArticlesController($templateCache, $modal, articlesData, Articles, $state) {
var articles = this;
articles.data = articlesData.data;
console.log($modal);//Give me LOG: Object{open: function () { ... }}
articles.open = function (article) {
var modalInstance = $modal.open({ // The unit test point to this line
template: $templateCache.get('articles/templates/modalDestroy.html'),
controller: 'ArticleDestroyController',
controllerAs: 'article',
size: 'sm',
resolve: {
articleData: function(){
return article;
}
}
});
modalInstance.result.then(function (article) {
Articles.destroy(article._id).then(function(response) {
var data = [];
angular.forEach(articles.data, function(value, key) {
if( value._id !== article._id){
this.push(value);
}
}, data);
articles.data = data;
})
.catch(function(response) {
console.log(response);
});
});
};
}
function ArticleDestroyController($modalInstance, articleData, Articles) {
var article = this;
article.data = articleData;
article.ok = function () {
$modalInstance.close(articleData);
};
article.cancel = function () {
$modalInstance.dismiss('cancel');
};
}
angular.module('articles.controllers', [])
.controller('ArticlesController', ArticlesController)
.controller('ArticleDestroyController', ArticleDestroyController);
})();
TEST
'use strict';
/* global describe, beforeEach, it, before, after, afterEach, inject, expect, spyOn */
describe('Unit: ArticlesController', function() {
var $rootScope, $scope, $controller;
beforeEach(function() {
module('articles');
});
var fakeModal = {
result: {
then: function(confirmCallback, cancelCallback) {
//Store the callbacks for later when the user clicks on the OK or Cancel button of the dialog
this.confirmCallBack = confirmCallback;
this.cancelCallback = cancelCallback;
}
},
close: function( item ) {
//The user clicked OK on the modal dialog, call the stored confirm callback with the selected item
this.result.confirmCallBack( item );
},
dismiss: function( type ) {
//The user clicked cancel on the modal dialog, call the stored cancel callback
this.result.cancelCallback( type );
}
};
beforeEach(inject(function($modal) {
spyOn($modal, 'open').and.callFake(fakeModal);
}));
beforeEach(inject(function(_$rootScope_,_$controller_, $modal){
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$controller = _$controller_;
var articlesData = {data:[{title:'my title',content:'my content'}]};
$controller('ArticlesController as articles', {$scope: $scope, $modal: $modal, articlesData:articlesData});
}));
it('articles.data should create an array with at least one article object ', function() {
expect($scope.articles.data.length).toBe(1);
$scope.articles.open($scope.articles.data[0]);
});
});
When I run gulp unit
I've got
TypeError: undefined is not a function
at ArticlesController.articles.open
point to line 9
var modalInstance = $modal.open
but if I try to check $modal it gives me
console.log($modal);//Give me LOG: Object{open: function () { ... }}
Do you see what's the problem ?
spyOn($modal, "open").and.callFake(function() {
return fakeModal;
});
Ran into this exact issue at work and resolved it in this way based on the documentation for Jasmine 2.0(http://jasmine.github.io/2.0/introduction.html).
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.