Jasmine + Chutzpah: Expected undefined to be equal - javascript

I'm new in this angular world, so sorry if this is so basic, but I'm not getting it
I'm having this error whenever I try to run my jasmine tests
My controller is defined this way:
angular.module('Module',[]).controller('GACtrl', ['GASvc', '$scope', GACtrl]);
function GACtrl(SasService, $scope) {
var vm = this;
vm.prueba = 20;
Jasmine test:
describe("Tests", function () {
var $scope, ctrl;
var svcMock;
beforeEach(function(){
svcMock = jasmine.createSpyObj("GACtrl", ["GACtrl"]);
module('Module');
inject(function($rootScope, $controller){
$scope = $rootScope.$new();
});
});
it(", should show the connection between controller and tests", function () {
expect($scope.prueba).toEqual(20);
})
Visual Studio and Chutzpah keep showing me that error, this porject had a service.js file, I only want to test controller functions, tests are in a separate VS project from Controller and Service.
Thanks and sorry for the bad English!

Related

testing angularjs 1 factory method is automatically called inside a controller with jasmine

I'm using ruby on rails with angularjs one, and testing it with teaspoon-jasmine for the first time and am running into issues. Basically, I have a controller that creates an empty array and upon load calls a factory method to populate that array. The Factory makes an http request and returns the data. Right now, i'm trying to test the controller, and i'm trying to test that 1) the factory method is called upon loading the controller, and 2) that the controller correctly assigns the returned data through it's callback. For a while I was having trouble getting a mocked factory to pass a test, but once I did, I realized I wasn't actually testing my controller anymore, but the code below passes. Any tips on how I can still get it to pass with mock, promises/callbacks, but accurately test my controller functionality. Or should I even test the this at all in my controller since it calls a factory method and just gives it a callback? My 3 files are below. Can anyone help here? It would be greatly appreciated
mainController.js
'use strict';
myApp.controller('mainController', [ 'mainFactory', '$scope', '$resource', function(factory, scope, resource){
//hits the /games server route upon page load via the factory to grab the list of video games
scope.games = [];
factory.populateTable(function(data){
scope.games = data;
});
}]);
mainFactory.js
'use strict';
myApp.factory('mainFactory', ['$http', '$routeParams', '$location', function(http, routeParams, location) {
var factory = {};
factory.populateTable = function(callback) {
http.get('/games')
.then(function(response){
callback(response.data);
})
};
return factory;
}]);
And finally my mainController_spec.js file
'use strict';
describe("mainController", function() {
var scope,
ctrl,
deferred,
mainFactoryMock;
var gamesArray = [
{name: 'Mario World', manufacturer: 'Nintendo'},
{name: 'Sonic', manufacturer: 'Sega'}
];
var ngInject = angular.mock.inject;
var ngModule = angular.mock.module;
var setupController = function() {
ngInject( function($rootScope, $controller, $q) {
deferred = $q.defer();
deferred.resolve(gamesArray);
mainFactoryMock = {
populateTable: function() {}
};
spyOn(mainFactoryMock, 'populateTable').and.returnValue(deferred.promise);
scope = $rootScope.$new();
ctrl = $controller('mainController', {
mainFactory: mainFactoryMock,
$scope: scope
});
})
}
beforeEach(ngModule("angularApp"));
beforeEach(function(){
setupController();
});
it('should start with an empty games array and populate the array upon load via a factory method', function(){
expect(scope.games).toEqual([])
mainFactoryMock.populateTable();
expect(mainFactoryMock.populateTable).toHaveBeenCalled();
mainFactoryMock.populateTable().then(function(d) {
scope.games = d;
});
scope.$apply(); // resolve promise
expect(scope.games).toEqual(gamesArray)
})
});
Your code looks "non-standard" e.g still using scope.
If you are just starting with angular I hardly recommend you to read and follow this:
https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md
Angular controllers cannot be tested, extract the logic into factories/services and test from there.

Unit Testing a Controller with specific syntax

Whenever, I am testing a controller and have something like this in it.
$scope.isSomething = function (Item) {
return ItemCollection.someItem(Item.attachedItem);
};
giving error on karma console:
TypeError: undefined is not an object (evaluating 'Item.attachedItem')
I am simply calling the function from the test file like this:
scope.isSomething();
I need to mock the Item.attachedItem or I am missing something here.. Please Explain in details as this is happening in multiple files.. thanks in advance
Also, for this type of code
.controller('itemCtrl', function (itemCollection) {
var vm = this;
this.itemCollection= itemCollection;
itemCollection.someItem().then(function (Item) {
vm.pageUrl = Item.pageUrl;
vm.Item= Item.someItems;
});
});
Also, this is also part of the code for more broad view here it gives Item.pageUrl is not a object error
Refer angular unit testing docs
The ItemCollection being a service, you could inject a mock while initialising a controller using
var ItemCollection, ItemCrtl;
beforeEach(inject(function($controller, $rootScope) {
$scope = $rootScope.$new();
ItemCollection = jasmine.createSpyObj('ItemCollection', ['someItem']);
ItemCrtl = $controller('ItemCtrl', {
$scope: scope,
ItemCollection: ItemCollection
});
});
For Item, the method isSomething should take care of checking if Item is undefined before doing Item.attachedItem
Testing an aync block is tricky. someItem returns a promise. $q an angular service to which can be used create async functions while testing.
We need to resolve the deferred object to test the async task.
var ItemCollection, ItemCrtl, deferedObj;
beforeEach(inject(function($controller, $rootScope, $q) {
$scope = $rootScope.$new();
deferedObj = $q.defer();
ItemCollection = jasmine.createSpyObj('ItemCollection', ['someItem']);
ItemCollection.someItem.andReturn(deferedObj.promise);
ItemCtrl = $controller('ItemCtrl', {
$scope: scope,
ItemCollection: ItemCollection
});
});
it('sets page url', function() {
deferedObj.resolve({ pageUrl: 'http://url', someItems: [1,2,3] });
scope.$apply();
expect(ItemCtrl.pageUrl).toEqual('http://url');
});
you have to use mock Item data in test like this (assuming attachedItem value is boolean)
var item={attachedItem:true}
scope.isSomething(item)
$scope.isSomething = function (Item) {
if(!Item.attachedItem){
Item.attachedItem=YOUR_MOCK_VALUE;
}
return ItemCollection.someItem(Item.attachedItem);
};

How to inject window object in test spec file?

I have set up an AngularJS application on Electron (former atom-shell) and e2e-testing using Protractor. I would now like to unit-test it.
My attempt at testing it using command $ protractor conf.js result with an error:
ReferenceError: window is not defined
This error comes from requiring angular-mocks. Looking at angular-mocks.js, it takes window and window.angular as entry parameters. I don't know how I can inject it into my file to solve this error.
The code I'm trying to use:
test-spec.js
describe('appController', function () {
require('angular-mocks');
var $controller;
beforeEach(function () {
module('app');
});
beforeEach(inject(function (_$controller_) {
$controller = _$controller_;
}));
it('should return empty string',
function () {
var $scope = {};
var controller = $controller('appController',
{$scope:$scope});
var result = $scope.generateString(12, 'blue');
expect(result).toEqual("");
});
});
Do you know a way to solve this issue?

How to test angular app with John Papa style, Karma, and Jasmine with data service?

For some reason, even following the example provided by Josh in the post reply How to test John papa vm.model unit testing with jasmine?, I can't get my controller's values to show up in the testing area. I think it's because of the data service, but it is a necessary component for our SPA, as is using John Papa's styling.
Below is a code snippet to hold the code and display the errors I'm receiving.
(function() {
'use strict';
angular.module('tickets')
.service("DataService", DataService)
/* #ngInject */
DataService.$inject = ["$rootScope", "$q"];
function DataService($rootScope, $q) {
var vm = this;
vm.nameDefault = "Name -- Please Authenticate";
vm.name = vm.nameDefault;
};
})();
(function() {
'use strict';
angular.module('tickets')
.controller('HomeController', HomeController);
/* #ngInject */
HomeController.$inject = ['$scope', '$location', 'DataService'];
function HomeController($scope, $location, DataService) {
var vm = this;
vm.name = DataService.name;
vm.nameDefault = DataService.nameDefault;
};
})();
<link href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.1.0/jasmine.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.1.0/jasmine.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.1.0/jasmine-html.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.1.0/boot.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.4/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-beta.4/angular-mocks.js"></script>
<script>
'use strict';
describe('Controller: HomeController', function() {
beforeEach(module('tickets'));
var controller, $location, DataService;
var tests = 0;
beforeEach(inject(function($controller, _$location_, _DataService_) {
$location = _$location_;
DataService = _DataService_;
scope = {};
controller = $controller('HomeController', {});
}));
var controller, scope, $location, DataService;
var tests = 0;
/* // This version works up until I try to verify the name and nameDefault in controller \\
*
* beforeEach(inject(function ($rootScope, $controller, _$location_, _DataService_) {
* $location = _$location_;
* DataService = _DataService_;
* scope = $rootScope.$new();
*
* controller = function () {
* return $controller('HomeController', {});
* };
* }));
*/
afterEach(function() {
tests += 1;
});
describe('local variables', function() {
describe('load the data model and values for all pertinent variables', function() {
it('should be instantiated', function() {
expect(DataService).toBeDefined();
});
it('should contain a name with an initial value before authentication', function() {
expect(DataService.nameDefault).toBe('Name -- Please Authenticate');
expect(DataService.name).toEqual(DataService.nameDefault);
});
});
describe('should load the controller with data model values, and update as data model values update', function() {
it('should be instantiated', function() {
expect(controller).toBeDefined();
})
it('should not have a vm attribute that can be reached from here', function() {
expect(controller.vm).toBeUndefined();
})
it('should contain a name with an initial value before authentication, both from the data model', function() {
expect(controller.name).toBe(DataService.name);
expect(controller.nameDefault).toBe(DataService.nameDefault);
});
});
});
it('should have tests', function() {
expect(tests).toBeGreaterThan(0);
});
});
</script>
My code, when I use it in our native environment, works to verify that everything in the data service has been instantiated properly (and using the commented out beforeEach block), but the styling using the example in the question referenced above throws even more errors about the scope not being instantiated, even though it is the same (with added dependencies) as that question.
I would expect the answer to be similar to the (currently unanswered) question: How to test John papa vm.model controllers and factories unit testing with jasmine?
I appreciate any help you guys offer.
-C§
Edit Even though I've answered and have success, I would love to get some feedback on why the implementation below works and the other two attempts do not. This is using Karma version 0.13.8 (latest), jasmine 2.1.0, and Angular 1.4.0.
I know it seems like I came up with the solution pretty quickly, but I've been wrestling with this since Friday afternoon (8/7) and have tried a dozen different formats without success.
Again, I welcome your comments and votes so that I can better understand why the version below works and the others have not, especially since I am still very green to AngularJS (1 month in, now).
Thanks again,
-C§
I get it now. Just had to look at Globally mock object in angularjs for jasmine/karma testing and my brain clicked.
The declaration and beforeEach block in the beginning of the test needs to look like this:
var controller, scope, $location, DataService;
var tests = 0;
beforeEach(inject(function ($rootScope, $controller, _$location_, _DataService_) {
$location = _$location_;
DataService = _DataService_;
scope = $rootScope.$new();
controller = $controller('HomeController', {
$scope: scope
});
}));
I think since I've messed with our initial setup a little too much (started the SPA from a template), I needed a strange implementation to make it all work. I'm now getting successful tests throughout:
I hope this helps someone else with similar issues.

Test Angular scope variables inside ajax request with Jasmine

I would like to know how to test some Angular scope variables at my controller that was created inside an ajax request.
What I mean is... This is my controller:
app.controller('NewQuestionCtrl', function ($scope, Question) {
loadJsonAndSetScopeVariables($scope, Question);
});
function loadJsonAndSetScopeVariables(scope, Question) {
Question.loadJson().then(function(success) {
var result = success.data.variables;
scope.levels = result.levels;
scope.tags = result.tags;
scope.difficulties = result.difficulties;
scope.questionTypes = result.questionTypes;
scope.areas = result.areas;
},function(data){
});
}
One of the prerequisites is not to use mock.
At my test I was able to inject my Question service:
describe('Controller: NewQuestionCtrl', function () {
beforeEach(angular.mock.module('testmeApp'));
var NewQuestionCtrl, scope, QuestionService;
beforeEach(inject(function ($controller, $rootScope, Question) {
scope = $rootScope.$new();
QuestionService = Question;
NewQuestionCtrl = $controller('NewQuestionCtrl', {
$scope: scope
});
}));
it('should attach a list of areas to the scope', function (done) {
expect(scope.areas).toBeDefined();
done();
});
Please, someone could help me?
Create a mock for Question and use that. There are several ways to do this. This is just one of them.
You could alternatively inject a real instance of Question and spy on that instead, but a mock is preferred to isolate these unit tests from the Question unit tests.
var questionDeferred, myController, scope;
var mockQuestion = {
loadJson: angular.noop
};
beforeEach(inject(function($q, $rootScope, $controller) {
questionDeferred = $q.defer();
scope = $rootScope.$new();
spyOn(mockQuestion, 'loadJson').and.returnValue(questionDeferred);
// Because your function is run straight away, you'll need to create
// your controller in this way in order to spy on Question.loadJson()
myController = $controller('NewQuestionCtrl', {
$scope: scope,
Question: mockQuestion
});
}));
it('should attach a list of areas to the scope', function (done) {
questionDeferred.resolve({/*some data*/});
scope.$digest();
expect(scope.areas).toBeDefined();
done();
});

Categories

Resources