Jasmine - test function invoking variable - javascript

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

Related

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

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();
});

How to test my http request in my app

I am trying to write units test for my app and I have the following issue
In my controller, I have something like
$scope.test1 = function() {
productFactory.getName()
.then(function(products){
$scope.result = products;
})
}
productFactory
angular.module('myApp').factory('productFactory', function($http) {
var factoryObj = {};
factoryObj.getName = function() {
return http.get(url)
}
return factoryObj
})
In my unit test file
describe('test here', function () {
var testCtrl, scope, httpBackend, mockFactory;
beforeEach(module('myApp', function($provide){
$provide.value('productFactory', mockFactory);
}));
// Initialize the controller and a mock scope
beforeEach(inject(function (_$controller_, _$rootScope_, _$httpBackend_, _productFactory_) {
scope = _$rootScope_.$new();
httpBackend = _$httpBackend_;
mockFactory = _productFactory_;
testCtrl = _$controller_('testCtrl', {
$scope: scope
});
it('should get product name', function() {
scope.test1();
//I am not sure how to test the results
});
}));
When I run karma test, it gives me
TypeError: 'undefined' is not an object (evaluating 'productFactory.getName()')
I am not sure how to test the http result and fix the error. Can anyone help me about it? Thanks a lot!
First of all, you don't need to worry about using $provide:
beforeEach(module('myApp'));
1. Without $httpBackend (mock out the service completely)
Then, productFactory will be passed into your controller, but you want to spyOn the getName():
// Initialize the controller and a mock scope
beforeEach(inject(function (_$controller_, _$rootScope_, _$httpBackend_, _productFactory_) {
scope = _$rootScope_.$new();
httpBackend = _$httpBackend_;
mockFactory = _productFactory_;
// add spy for the method, wrap with $q.when so it returns a promise
spyOn(mockFactory, 'getName').and.returnValue($q.when('Pizza!'));
testCtrl = _$controller_('testCtrl', {
$scope: scope,
productFactory: mockFactory // pass in here
});
Then, you've got to cause a $digest cycle, so that the promise will call through:
it('should get product name', function() {
scope.test1();
// hit the $digest
scope.$apply();
// expectation
expect(scope.result).toBe('Pizza!')
});
2. With $httpBackend
// Initialize the controller and a mock scope
beforeEach(inject(function (_$controller_, _$rootScope_, _$httpBackend_) {
scope = _$rootScope_.$new();
httpBackend = _$httpBackend_;
// set up httpBackent
httpBackend.when('GET', '/products')
.respond([{ name: 'Pizza!'}, {name: 'Sandwich'}]);
testCtrl = _$controller_('testCtrl', {
$scope: scope
});
We don't need to mock the factory in this case at all. Then, we just need to flush $httpBackend when we want the http call to return:
it('should get product name', function() {
scope.test1();
// hit the $digest with flush
httpBackend.flush();
// expectation
expect(scope.result.length).toBe(2)
});

How to test a directive's controller using angularJS-karma-jasmine?

Goal:
Write a passing test for the waCarousel directive scope variable: self.awesomeThings. Expect this test pass when self.awsomeThings.length.toBe(3) to is true?
Question:
How can I properly write this test? rather how do I inject a directives controller?
Directive:
angular.module('carouselApp')
.directive('waCarousel', function() {
return {
templateUrl: '../../../views/carousel/wa.carousel.html',
controller: function($scope) {
var self = this;
self.awesomeThings = [1, 2, 3];
return $scope.carousel = self;
}
}
});
Unit Test:
describe('waCarousel Unit', function() {
// am I missing a $controller & namespace variable init?
var $compile,
$rootScope;
// Load the myApp module, which contains the directive
beforeEach(module('carouselApp'));
// Store references to $rootScope and $compile and $controller
// so they are available to all tests in this describe block
beforeEach(inject(function(_$compile_, _$rootScope_, _$controller_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$compile = _$compile_;
$rootScope = _$rootScope_;
$controller = _$controller_;
// WaCarouselCtrl = $controller('WaCarouselCtrl', {
// $scope: scope
// });
}));
it('should have a list of awesomeThings', function() {
// This wont pass
expect(scope.awesomeThings.length).toBe(3);
});
});
This is how I would do it for a typical view and not directive:
describe('Controller: MainCtrl', function() {
// load the controller's module
beforeEach(module('carouselApp'));
var MainCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
// !!*** this is how I would inject the typical controller of a view **!! //
MainCtrl = $controller('MainCtrl', {
$scope: scope
});
}));
it('should attach a list of awesomeThings to the scope', function() {
expect(scope.awesomeThings.length).toBe(3);
});
});
How do I merge these two concepts so that I can expect self.awesomeThings.length).toBe(3)?
UPDATE:
Compile the element, and after calling $digest(), you will have access to the scope which contains carousel object with awesomeThings array:
describe('waCarousel Unit', function() {
var scope;
beforeEach(module('carouselApp'));
beforeEach(inject(function($rootScope, $compile) {
var element = '<test></test>';
scope = $rootScope.$new();
element = $compile(element)(scope);
scope.$digest();
}));
it('should have a list of awesomeThings', function() {
expect(scope.carousel.awesomeThings.length).toBe(3);
});
});
Also, here are some useful links to testing directives in angular:
Testing Directives
Testing AngularJS directive controllers with Jasmine and Karma
Introduction to Unit Test: Directives

$scopeProvider <- $scope/ Unknown provider

I testing my angular-application with jasmine(http://jasmine.github.io/2.0/) and getting next error:
Unknown provider: $scopeProvider <- $scope
I know, that it's incorrect to build dependency with scope in filters, services, factories, etc., but I use $scope in controller!
Why am i getting this error? controller looks like
testModule.controller('TestCont', ['$filter', '$scope', function($filter, $scope){
var doPrivateShit = function(){
console.log(10);
};
this.lol = function(){
doPrivateShit();
};
this.add = function(a, b){
return a+b;
};
this.upper = function(a){
return $filter('uppercase')(a);
}
$scope.a = this.add(1,2);
$scope.test = 10;
$scope.search = {
};
}]);
and my test's code:
'use strict';
describe('testModule module', function(){
beforeEach(function(){
module('testModule');
});
it('should uppercase correctly', inject(function($controller){
var testCont = $controller('TestCont');
expect(testCont.upper('lol')).toEqual('LOL');
expect(testCont.upper('jumpEr')).toEqual('JUMPER');
expect(testCont.upper('123azaza')).toEqual('123AZAZA');
expect(testCont.upper('111')).toEqual('111');
}));
});
You need to manually pass in a $scope to your controller:
describe('testModule module', function() {
beforeEach(module('testModule'));
describe('test controller', function() {
var scope, testCont;
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
testCont = $controller('TestCont', {$scope: scope});
}));
it('should uppercase correctly', function() {
expect(testCont.upper('lol')).toEqual('LOL');
expect(testCont.upper('jumpEr')).toEqual('JUMPER');
...
});
});
});
Normally, a $scope will be available as an injectable param only when the controller is attached to the DOM.
You need to associate somehow the controller to the DOM (I'm mot familiar with jasmine at all).
I am following a video tutorial from egghead (link bellow) which suggest this approach:
describe("hello world", function () {
var appCtrl;
beforeEach(module("app"))
beforeEach(inject(function ($controller) {
appCtrl = $controller("AppCtrl");
}))
describe("AppCtrl", function () {
it("should have a message of hello", function () {
expect(appCtrl.message).toBe("Hello")
})
})
})
Controller:
var app = angular.module("app", []);
app.controller("AppCtrl", function () {
this.message = "Hello";
});
I am posting it because in the answer selected we are creating a new scope. This means we cannot test the controller's scope vars, no?
link to video tutorial (1min) :
https://egghead.io/lessons/angularjs-testing-a-controller

Angularjs Unit Test for Service

I am consuming a service for which i am writing unit test case. When i inject the service & call the function from controller, i do not get the data. I am beginner to write cases.
Here is my code.
StatesList Service
angular.module('myApp').factory('StatesList', ['$resource', function($resource) {
return $resource('/api/states');
}]);
Controller
$scope.statesList = function () {
StatesList.query(function (states) {
// Brings all states
$scope.states = states;
});
};
Test
describe('States List', function () {
var ctrl, scope, statesService;
beforeEach(function () {
module('myApp');
inject(function ($rootScope, $controller, StatesList) {
scope = $rootScope.$new();
statesService = StatesList;
ctrl = $controller('StatesCtrl', { $scope: scope, StatesList: statesService });
});
});
it('should have practice list to be null', function () {
console.log('List of States');
scope.statesList();
console.log(scope.states); // I don't see any data here
expect(scope.states).not.toBeNull();
});
Output in WebStorm
'List of States'
undefined
Why the states don't get displayed. By using POSTMAN data can be seen.
StatesList.query() is an asynchronous http call, so you need to use mock $httpBackend service from ngMock module in your test. Add angular-mock.js to your test config, then try this:
describe('States List', function () {
var ctrl, scope, statesService, $httpBackend;
beforeEach(function () {
module('myApp');
inject(function ($rootScope, $controller, StatesList, _$httpBackend_) {
scope = $rootScope.$new();
statesService = StatesList;
ctrl = $controller('StatesCtrl', { $scope: scope, StatesList: statesService});
$httpBackend = _$httpBackend_;
});
});
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should have practice list to be null', function () {
$httpBackend.expectGET('/api/states').respond([{ // ask mock $httpBackend to respond with fake data
name: 'State 1'
}, {
name: 'State 2'
}]);
console.log('List of States');
scope.statesList();
$httpBackend.flush(); // flush the http request to send fake data back to StatesList.query()
console.log(scope.states); // I don't see any data here
expect(scope.states).not.toBeNull();
});
});

Categories