Jasmine unit test to check the scope of an Angular Bootstrap modal - javascript

I have a list of items. When you click on an item, it brings up a modal to display data for that item. In the controller for the list, there's a function, openRecentsModal, that takes the data object from the ng-repeat list, and creates it on a new scope when the function is run. The new modal then has that object available as $scope.recentsFoldersData. I need to write a unit test to ensure recentsFolderData is defined on the scope, but everything I've tried results in "expected undefined to be defined." I'm hoping somebody can help.
Here's the method in the list controller to open a modal:
function openRecentsModal(obj) {
var scope = $rootScope.$new();
scope.recentsFoldersData = obj;
var controller = 'recentsFoldersDetailController';
$modal.open({
scope: scope,
controller: controller,
templateUrl: 'js/modal/recents/folder/recentsFoldersDetail.tpl.html'
});
}
Here's the modal's controller:
angular.module('modal.recents.folder', [])
.controller('recentsFoldersDetailController', recentsFoldersDetailController);
recentsFoldersDetailController.$inject = ['$scope', '$modalInstance'];
function recentsFoldersDetailController($scope, $modalInstance) {
$scope.close = function close() {
$modalInstance.dismiss('close');
};
}
Finally, here's the unit tests I'm working on (I've excluded the ones that are passing, as well as the helper functions that aren't needed for this test):
describe('recents folders modal controller tests', function() {
var scope, q, modal, mockDetailController, mockListController, mockRecentService, mockFolderService, mockModalInstance, $httpBackend;
beforeEach(module('mainApp'));
beforeEach(inject(function($rootScope, $q, $controller, $modal, _recentService_, _folderService_, $injector) {
q = $q;
scope = $rootScope.$new();
modal = $modal;
$httpBackend = $injector.get('$httpBackend');
$httpBackend.whenGET('js/modal/recents/folder/recentsFoldersDetail.tpl.html').respond(200, '');
mockRecentService = _recentService_;
mockFolderService = _folderService_;
mockModalInstance = {
dismiss: jasmine.createSpy('modalInstance.dismiss')
};
mockDetailController = function() {
return $controller('recentsFoldersDetailController', {
'$scope': scope,
'$modalInstance': mockModalInstance
});
};
mockListController = function() {
return $controller('recentsListFoldersController', {
'$scope': scope,
'$modal': modal,
'recentService': mockRecentService,
'folderService': mockFolderService
});
};
}));
describe('scope tests', function() {
it('should place the data on the scope when openRecentsModal is called', function() {
var obj = defaultSuccessfulRecentsDataResponse();
mockListController();
spyOn(scope, 'openRecentsModal');
scope.openRecentsModal(obj);
expect(scope.openRecentsModal).toHaveBeenCalledWith(obj);
mockDetailController();
expect(scope.recentsFoldersData).toBeDefined();
});
});
/* helper functions */
function defaultSuccessfulRecentsDataResponse() {
return {
id: 'id 1',
name: 'first name',
description: 'first description'
};
}
});

I was able to fix this with a simple change to the function calling the modal
function openRecentsModal(obj) {
$rootScope.recentsFoldersData = obj;
var controller = 'recentsFoldersDetailController';
$modal.open({
//scope: scope,
controller: controller,
templateUrl: 'js/modal/recents/folder/recentsFoldersDetail.tpl.html'
});
}
By allowing the object to be placed on $rootScope (UI-Bootstrap modal's default setting), rather than a new $scope, the test came back with a defined value
it('should put recents object on the scope', function() {
mockListController();
scope.openRecentsModal(defaultSuccessfulRecentsDataResponse());
mockDetailController();
expect(scope.recentsFoldersData).toBeDefined();
});

Related

How to test that my service returns data with jasmine and angularjs

I've begun using jasmine to test my controllers in angularjs however after reading some tutorials I am a bit stuck.
I have this simple angularjs controller called jasmineController
(function () {
"use strict";
var myAppModule = angular.module('myApp');
myAppModule.controller('jasmineController', ['$scope', 'genericService',
function ($scope, genericService) {
$scope.name = 'Superhero';
$scope.counter = 0;
$scope.$watch('name', function (newValue, oldValue) {
$scope.counter = $scope.counter + 1;
});
$scope.testPromise = function() {
return genericService.getAll("dashboard", "currentnews", null, null, null);
}
$scope.getNewsItems = function () {
genericService.getAll("dashboard", "currentnews", null, null, null).then(function (data) {
$scope.name = 'Superhero';
$scope.newsItems = data;
});
}
}
]);
})();
In my jasmine test I wanted to call getNewsItems and check that it can call genericService.getAll and that $scope.newsItems is assigned some data. I understand that I would be mocking out the service and I won't actually call it.
Here is my spec
describe("test", function () {
// Declare some variables required for my test
var controller, scope, genericService;
// load in module
beforeEach(module("myApp"));
beforeEach(inject(function ($rootScope, $controller, _genericService_) {
genericService = _genericService_;
// assign new scope to variable
scope = $rootScope.$new();
controller = $controller('jasmineController', {
'$scope': scope
});
}));
it('sets the name', function () {
expect(scope.name).toBe('Superhero');
});
it('should assign data to scope', function() {
//var fakeHttpPromise = {success: function () { }};
scope.getNewsItems();
spyOn(genericService, 'getAll');
expect(genericService.getAll).toHaveBeenCalledWith('dashboard', 'currentnews');
});
});
I've got a spyon for genericService.getall() but apart from that I am a bit stuck with checking that my scope variable is assigned a value.
I also get this stack trace:
Error: Expected spy getAll to have been called with [ 'dashboard', 'currentnews' ] but it was never called.
at stack (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1441:11)
at buildExpectationResult (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1408:5)
at expectationResultFactory (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:533:11)
at Spec.prototype.addExpectationResult (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:293:5)
at addExpectationResult (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:477:9)
at Anonymous function (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1365:7)
at Anonymous function (file:///C:/Projects/2013/AMT2015/AMT2015.WebAPP/Scripts/tests/controllers/dashboardControllerSpec.js:49:9)
at attemptSync (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1759:9)
at QueueRunner.prototype.run (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1747:9)
at QueueRunner.prototype.execute (file:///C:/Users/nickgowdy/Local%20Settings/Application%20Data/Microsoft/VisualStudio/12.0/Extensions/4sg2jkkc.gb4/TestFiles/jasmine/v2/jasmine.js:1733:5)
You need to put the spy first before calling the test function. And you are you actually passing more parameters to the service function. So you need to test with the exact parameter list.
it('should assign data to scope', function() {
//var fakeHttpPromise = {success: function () { }};
spyOn(genericService, 'getAll');
scope.getNewsItems();
expect(genericService.getAll).toHaveBeenCalledWith('dashboard', 'currentnews',null,null,null);
});
I ended up doing this:
describe("test", function () {
// Declare some variables required for my test
var controller, scope, genericService;
// load in module
beforeEach(module("myApp"));
beforeEach(inject(function ($rootScope, $controller, _$q_, _genericService_) {
genericService = _genericService_;
var deferred = _$q_.defer();
deferred.resolve('resolveData');
spyOn(genericService, 'getAll').and.returnValue(deferred.promise);
scope = $rootScope.$new();
controller = $controller('jasmineController', {
'$scope': scope
});
}));
it('sets the name', function () {
expect(scope.name).toBe('Superhero');
});
it('should assign data to scope', function() {
//spyOn(genericService, 'getAll').and.callFake(function() {
//});
scope.getNewsItems();
scope.$apply();
expect(scope.newsItems).toBe('resolveData');
//expect(genericService.getAll).toHaveBeenCalledWith('dashboard', 'currentnews', null, null, null);
});
});
Because my test is more than just calling a service but handling a promise as well I had to inject $q. Then with spy on I say to call service and method and the return value is the deferred promise.
Finally I can look at the scope variable to see if anything is assigned with this line:
expect(scope.newsItems).toBe('resolveData');
Thanks to everyone that helped.

Invoking service method in Jasmine

Using Angular and Jasmine I would like to run the service method with some mockup data. Below is the code of my test which uses some working RoomsController trying to run test() method on the RoomsParamsSvc:
describe('Rooms Controller', function() {
var RoomsController,
scope,
location,
httpBackend,
RoomsParamsSvc;
beforeEach(module('rooms', function ($provide, $injector) {
RoomsParamsSvc = function () { //(1a)
return $injector.get('RoomsParamsSvc'); //(1b)
}; //(1c)
$provide.value('RoomsParamsSvc', RoomsParamsSvc); //(1d)
}));
beforeEach(inject(function($controller, $rootScope, $location, $httpBackend, _RoomsParamsSvc_) {
// Set a new global scope
scope = $rootScope.$new();
location = $location;
httpBackend = $httpBackend;
RoomsParamsSvc = _RoomsParamsSvc_;
RoomsController = $controller('RoomsController', {
$scope: scope,
$location: location,
RoomsParamsSvc: RoomsParamsSvc
});
}));
it('should have test as a function', function () {
var t = RoomsParamsSvc.test();
});
});
As far as I understand with the with injector I should be able to use that injected service. Without (1a-1d) I got an error:
Error: [$injector:unpr] Unknown provider: RoomsParamsSvcProvider <-
RoomsParamsSvc
However now it doesn't work, too. I got an error meaning that test() is not a function:
jasmine typeerror 'undefined' is not a function (evaluating 'RoomsParamsSvc.test()')
My service looks like that:
var roomsApp = angular.module('rooms', []);
roomsApp.factory('RoomsParamsSvc', function () {
var factory = {};
factory.test = function ()
{
return '';
}
return factory;
});
Do you have any suggestions?
Lines 1a-1d are not required, as the 'RoomsParamsSvc' is loaded within your 'room' module. But you make a reference to the RoomsController, which is undefined.
beforeEach(module('rooms'));
beforeEach(inject(function($controller, $rootScope, $location, $httpBackend, _RoomsParamsSvc_) {
// Set a new global scope
scope = $rootScope.$new();
location = $location;
httpBackend = $httpBackend;
RoomsParamsSvc = _RoomsParamsSvc_;
RoomsController = $controller(function() {}, {
$scope: scope,
$location: location,
RoomsParamsSvc: RoomsParamsSvc
});
console.log(RoomsParamsSvc);
}));
Plunker

Testing Angular with parent controller method call

I'm trying to write unit-tests for an Angular application for the first time. Currently i'm having some problems running the tests. Running the application normally works fine, it doesn't give any errors. However, when running the tests using Karma and Jasmine i'm getting the following error:
TypeError: 'undefined' is not a function (evaluating '$scope.addActiveClassToMenuButton('menuButtonHome')')
I'm using the ui.router module. Not sure if that matters.
Parent controller
Parent controller contains the following method:
angular.module('testApp')
.controller('ParentCtrl', function ($scope, $resource) {
$scope.addActiveClassToMenuButton = function(buttonId) {
//Some code
}
}
Child controller
Child controller calls the parents method like this:
angular.module('testApp')
.controller('ChildCtrl', function ($scope, $resource) {
$scope.addActiveClassToMenuButton('menuButtonHome');
}
Child controller test file
The test file that fails:
describe('Child controller tests. ', function () {
beforeEach(module('testApp'));
var ChildCtrl, scope;
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
ChildCtrl = $controller('ChildCtrl', {
$scope: scope
});
}));
it('simple false test', function () {
expect(false).toBe(false);
});
});
Even though i'm not using the scope in the test yet, all tests fail because the code can't find the parents method.
Solution
Changing the test file to this worked:
describe('Child controller tests. ', function () {
beforeEach(module('testApp'));
var controller, scope, parentScope, childScope;
beforeEach(inject(function ($controller, $rootScope, $compile) {
scope = $rootScope.$new();
var el = angular.element('<div ng-controller="ParentCtrl"><div ng-controller="ChildCtrl"></div></div>');
$compile(el)(scope);
parentScope = el.scope();
childScope = el.children().scope();
}));
it('simple false test', function () {
expect(false).toBe(false);
});
});
Try this..
describe('Child controller tests. ', function () {
beforeEach(module('testApp'));
var ChildCtrl, scope;
beforeEach(inject(function ($controller, $rootScope, $compile) {
scope = $rootScope.$new();
var el = angular.element('<div ng-controller="ParentCtrl"><div ng-controller="ChildCtrl"></div></div>');
$compile(el)(scope);
// to access parent controller.
var parentScope = el.scope();
var childScope = el.children().scope();
// now you should be able to access from parent and child scopes.
}));
it('simple false test', function () {
expect(false).toBe(false);
});
});
This will instantiate ParentCtrl first and then extend the scope of it with the ChildCtrl's scope.
In the example that you have given only ChildCtrl is instantiated ParentCtrl is not instantiated.

Jasmine - test function invoking variable

I am trying to test a scenario when a service method is invoked through a local variable within an angular controller.
In this situation, when the items array is 0, a create new item modal would be triggered through the modal service.
Controller:
(function() {
'use strict';
angular
.module('app')
.controller('Item', Item);
//items is resolved through the ui-router resolve
//items contains a array of item objects. Will be an empty array if there are no items for that user
Item.$inject = ['items', 'modalService'];
function Item(items, modalService) {
var vm = this;
vm.items = items;
vm.newItemModal = modalService.newItemModal;
if (vm.items !== undefined) {
if (vm.items.length === 0) {
vm.newItemModal();
}
}
}
})();
vm.newItemModal() triggers the new item modal to be displayed. However, how do I test this scenario in jasmine?
Test so far:
describe('Controller: Item', function(){
var scope,
ctrl,
items,
modalService,
mockItems = [{ name: 'item1', desc:'desc1'}, { name: 'item2', desc:'desc2'}];
//mocking the modalService
beforeEach(function(){
module(function($provide){
modalService = {
newItemModal: function(){
return;
}
};
$provide.value('modalService', modalService);
});
});
beforeEach(inject(function(_$rootScope_, $controller) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
ctrl = $controller('Item as item', {
$scope: scope,
items: mockItems
});
}));
it('should verify the vm object', function(){
expect(scope.item.newItemModal).toBeDefined();
expect(scope.item.items).toEqual(mockItems);
});
//Separate test-suite as items is initialised with an empty array
describe('new item modal', function(){
beforeEach(inject(function(_$rootScope_, $controller) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
ctrl = $controller('Item as item', {
$scope: scope,
items: []
});
it('should open a new item modal', function(){
//returns 0
console.log('Items length', scope.items.length);
spyOn(scope.item, 'newItemModal').and.callThrough();
//testing this assertion fails
expect(scope.item.newItemModal).toHaveBeenCalled();
});
}));
});
});
The problem is that when the following line is executed:
spyOn(scope.item, 'newItemModal').and.callThrough();
The controller has already been created and it's code executed.
You need to set up your spy before the controller is created.
Example:
var createController;
beforeEach(inject(function(_$rootScope_, $controller) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
createController = function() {
$controller('Item as item', {
$scope: scope,
items: []
});
};
}));
it('should open a new item modal', function() {
spyOn(modalService, 'newItemModal').and.callThrough();
createController();
expect(scope.item.newItemModal).toHaveBeenCalled();
});
Note that you can not spy on scope.item since it isn't created until the controll is, so you will have to spy on the modalService instead.
Demo: http://plnkr.co/edit/y0vzfaqDSuuCuPVwdybq?p=preview

Unit testing $modal with Jasmine

I have an Angular app with a controller which displays an Angular-Strap modal window during a function call. It functions correctly in Chrome, but I am at a loss getting a valid unit test working.
App module and the FooController:
var app = angular.module("app", ["mgcrea.ngStrap"]);
app.controller("FooController", function($scope, $modal) {
var fooModal = $modal({
title: 'Foo',
content:'Bar',
show: false,
html: true,
backdrop: 'static',
placement: 'center'});
angular.extend($scope, {
makeItFoo: function() {
fooModal.show();
}
});
});
Controller spec:
describe('FooController', function () {
var scope, controller, modal;
beforeEach(module('app', function ($provide) {
// Stub out $modal service
$provide.value('$modal', function () {
return {
hide: function () { },
show: function () { }
};
});
}));
beforeEach(inject(function ($rootScope, $controller, $injector) {
//set up a new scope and the controller for the test
scope = $rootScope.$new();
controller = $controller('FooController', {$scope: scope});
modal = $injector.get('$modal');
}));
it('should show the modal', function () {
var modalSpy = spyOn(modal(), 'show');
scope.makeItFoo();
expect(modalSpy).toHaveBeenCalled();
});
});
Here's a fiddle as well.
I expect my call to makeItFoo() to display the modal, but Jasmine fails the test with the error Expected spy show to have been called. I've also tried setting the show property of the modal to true and not calling show() separately, and I've tried other variants of stubbing the $modal service and injecting it directly into the controller, but it ends up with the same error.
I'm using AngularJS 1.2.14, Angular-Strap 2.0.0, and Jasmine 1.3.1.
Instead of doing these. Create a mock object for $modal with show and hide methods and set your expectations on them.
describe('FooController', function () {
var scope, controller, modal;
beforeEach(module('app'));
beforeEach(inject(function ($rootScope, $controller) {
//set up a new scope and the controller for the test
scope = $rootScope.$new();
//Create spy object
modal = jasmine.createSpyObj('modal', ['show', 'hide']);
//provide modal as dependency to the controller.
controller = $controller('FooController', {$scope: scope, $modal:modal});
}));
it('should show the modal', function () {
scope.makeItFoo();
expect(modal.show).toHaveBeenCalled();
});
});
The modal show is async. I updated your fiddle at http://jsfiddle.net/jwom7ns2/1/.
Change the following portion:
it('should show the modal', function (done) {
var modalSpy = spyOn(modal(), 'show');
scope.makeItFoo();
setTimeout(function() {
expect(modalSpy).toHaveBeenCalled();
done();
});
});
The timeout wrapper waits for the digest to happen when the modal show occurs.

Categories