How to inject window object in test spec file? - javascript

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?

Related

Angular Service becomes undefined when spyOn it

I have an Angular 1.6.6 application which I'm testing with Karma and Jasmine.
Given this code from controller:
$scope.undo = function () {
return $scope.isUndoDisabled() || $scope.undoAction();
};
$scope.isUndoDisabled = function () {
return HistoryService.isUndoDisabled();
};
I have been testing it with the following spec:
it('undoAction should not be called with default settings', function () {
var $scope = {};
var controller = $controller('PaintController', { $scope: $scope });
spyOn($scope, 'undoAction');
//spyOn(HistoryService, 'isUndoDisabled');
$scope.undo();
expect($scope.undoAction).not.toHaveBeenCalled();
});
And is passing the test, but when I uncomment the spyOn of HistoryService, the call HistoryService.isUndoDisabled() from $scope.isUndoDisabled returns undefined and then the test fails because:
Expected spy undoAction not to have been called.
Any idea about what's going on???? It seems like the spyOn is affecting to the code??
spyOn(...) is a shortcut for spyOn(...).and.stub(), not spyOn(...).and.callThrough(). When being spied this way, HistoryService.isUndoDisabled() returns undefined.
The proper way to test the unit is to isolate it from others. Since it is the controller that is tested, the service should be mocked or stubbed:
spyOn(HistoryService, 'isUndoDisabled').and.returnValue(true);
And then in another test:
spyOn(HistoryService, 'isUndoDisabled').and.returnValue(false);
I think if you want to call isUndoDisabled() from HistoryService, the function $scope.isUndoDisabled should be
$scope.isUndoDisabled = function () {
HistoryService.isUndoDisabled();
};
There shouldn't be a return in the body

Jasmine + Chutzpah: Expected undefined to be equal

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!

How to unit test an Angular 1 controller that contains DOM/jquery selector?

When using jasmine, I can't seem to test a function that has a jquery selector or document.getElementById in it. Is there a better strategy here? I normally do not put selectors in angular code, however this is a workaround.
In the function I'm testing:
this.scope.login = () => {
($('#login-form')[0] as HTMLFormElement).submit();
// or even
document.getElementById('login-form').submit(); // this is a workaround for browser auto-complete, I normally would not have selectors in angular code.
}
I get
TypeError: undefined is not an object (evaluating '$('#login-form')[0].submit')
I've tried "mocking by spying," using spyOn to try to mock the jquery selector function and return a fake element... but doesn't seem to work, or I'm not doing it right.
My spec loads the template (logs ok). Element is also defined and seems to be a valid angular-compiled element.
describe('Nav Controller Spec', function() {
beforeEach(function() {
angular.mock.module('App');
inject(function(_$controller_, $rootScope, _userModel_, _$q_, _$httpBackend_, $templateCache, _$compile_) {
scope = $rootScope.$new();
$q = _$q_;
$compile = _$compile_;
$httpBackend = _$httpBackend_;
deferred = _$q_.defer();
html = $templateCache.get('main/components/login/login.tpl.html');
var el = angular.element( html );
element = $compile( el )(scope); //element is valid and works
controller = _$controller_;
userModel = _userModel_;
scope.userModel = userModel;
// tried this... still get error
spyOn($.fn, 'val').and.returnValue('<form></form>');
//if i change to 'init' or 'find', i get 'undefined is not a constructor'
spyOn($.fn, 'init').and.returnValue('<form></form>');
ctrl = controller('loginController', { $scope: scope, $element: element });
$rootScope.$digest();
});
});
it('should login and change the status', function(){
spyOn( ctrl.scope.userModel, 'login' ).and.callThrough();
ctrl.scope.formType = 'login';
ctrl.scope.login(); //fails
expect( ctrl.scope.userModel.login ).toHaveBeenCalled();
});
As a last resort, i tried the following with document.getElementById('login-form') in the controller. However, I get TypeError: undefined is not a constructor (evaluating 'document.getElementById('login-form').submit()')
var mockElement = {
id:"login-form",
parentNode:true
};
var document_getElementById = document.getElementById;
var spy = spyOn(document, "getElementById").and.callFake(function(id){
if(id===mockElement.id){
return mockElement;
}
return document_getElementById(id);
});
Actually, this works. You will want to stub/spy with document.getElementById since that's what jquery uses under the hood. I only had forgotten to stub the submit function. I didn't realize this because jasmine's wrapped errors are so meaningless.
var mockElement = {
id:"login-form",
parentNode:true,
submit:function(){
return 'cheese';
}
};
var document_getElementById = document.getElementById;
var spy = spyOn(document, "getElementById").and.callFake(function(id){
if(id===mockElement.id){
return mockElement;
}
return document_getElementById(id);
});

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

TypeError: jasmine.getEnv().currentSpec is null

When I try to run my jasmine specs, I get
TypeError: jasmine.getEnv().currentSpec is null in
http://localhost:8888/__JASMINE_ROOT__/jasmine.js (line 498)
No idea why, not even sure where to begin looking.
Line 498 is:
return jasmine.getEnv().currentSpec.expect(actual);
I've been doing jasmine for several months now, but not on this project. I've never seen this happening before.
So, where do I start?
(This is jasmine gem in a rails 3.x project)
I was seeing the same error. I had an expect statement directly inside describe. I moved the expect inside an it block an the error went away.
Thanks to rinat-io for the idea.
I think the problem is in different versions of angular in angular-mocks.js and angular-scenario.js
If your config looks like this:
files = [
JASMINE,
JASMINE_ADAPTER,
'../app/lib/angular/angular.js',
// ANGULAR_SCENARIO,
// ANGULAR_SCENARIO_ADAPTER,
'../app/lib/angular/angular-scenario.js',
'../app/lib/angular/jstd-scenario-adapter.js',
'../app/lib/angular/jstd-scenario-adapter-config.js',
'../app/lib/angular/angular-mocks.js',
'../app/lib/angular/angular-resource.js',
'../app/lib/angular/angular-cookies.js',
'../app/js/**/*.js',
'**/*Spec.js'
];
try to avoid ANGULAR_SCENARIO and ANGULAR_SCENARIO_ADAPTER - replace it with ones that are embedded into your angular source ('../app/lib/angular/angular-scenario.js', '../app/lib/angular/jstd-scenario-adapter.js', '../app/lib/angular/jstd-scenario-adapter-config.js' in my case).
What does your full test look like? I was also getting this error. My test looked like this:
'use strict';
describe("CalendarController", function() {
var scope, $location, $controller, createController;
var baseTime = new Date(2014, 9, 14);
spyOn(Date.prototype, 'getMonth').andReturn(baseTime.getMonth());
spyOn(Date.prototype, 'getDate').andReturn(baseTime.getDate());
spyOn(Date.prototype, 'getFullYear').andReturn(baseTime.getFullYear());
var expectedMonth = fixture.load("months.json")[0];
beforeEach(module('calendar'));
beforeEach(inject(function ($injector) {
scope = $injector.get('$rootScope').$new();
$controller = $injector.get('$controller');
createController = function() {
return $controller('CalendarController', {
'$scope': scope
});
};
}));
it('should load the current month with days', function(){
var controller = createController();
expect(scope.month).toBe(expectedMonth);
});
});
Notice that the SpyOn function is in the describe block. When looking at the jasmine code we find that SpyOn should be in a beforeEach or it block:
jasmine.Env.prototype.it = function(description, func) {
var spec = new jasmine.Spec(this, this.currentSuite, description);
this.currentSuite.add(spec);
this.currentSpec = spec;
if (func) {
spec.runs(func);
}
return spec;
};
...
jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
if (this.currentSuite) {
this.currentSuite.beforeEach(beforeEachFunction);
} else {
this.currentRunner_.beforeEach(beforeEachFunction);
}
};
These are the places where the currentSpec is set. Otherwise this will be null.
So in my example it should be:
'use strict';
describe("CalendarController", function() {
var scope, $location, $controller, createController;
var baseTime = new Date(2014, 9, 14);
var expectedMonth = fixture.load("months.json")[0];
beforeEach(module('calendar'));
beforeEach(inject(function ($injector) {
scope = $injector.get('$rootScope').$new();
$controller = $injector.get('$controller');
createController = function() {
return $controller('CalendarController', {
'$scope': scope
});
};
}));
it('should load the current month with days', function(){
spyOn(Date.prototype, 'getMonth').andReturn(baseTime.getMonth());
spyOn(Date.prototype, 'getDate').andReturn(baseTime.getDate());
spyOn(Date.prototype, 'getFullYear').andReturn(baseTime.getFullYear());
var controller = createController();
expect(scope.month).toBe(expectedMonth);
});
});
And then this will work because the spyOn is in the it block.
Hope this helps.
Check out this tweet, it has two fixes maybe one helps you out (they are related with the getEnv() method you are using:
https://twitter.com/dfkaye/statuses/423913741374074880
And the github links:
https://github.com/dfkaye/jasmine-intercept
https://github.com/dfkaye/jasmine-where
Maybe one of those will shed a light on your problem.
I was running into this issue and found the following article.
It seems as though the jasmine version and Angular versions were not working together. When I made the changes the article outlines to angular-mocks.js the error went away.
http://railsware.com/blog/2014/09/09/make-angularjs-1-0-7-work-with-jasmine-2-0/
I ran into this while taking the PluralSight course: Building a Site with Bootstrap, AngularJS, ASP.NET, EF and Azure. During the Unit testing module.

Categories

Resources