Jasmine test for method containing jQuery - angularjs

I have the following method in my controller:
$scope.onWindowResize = function() {
var dWidth= $('#div1').width();
var left = $('#div1').position().left;
$('.myClass1').css({'width':dWdth});
$('.myClass2').css({'left': left});
}
I am trying to test the method onWindowResize() with the below code.
it('onWindowResize()', inject(function () {
scope.onWindowResize();
scope.$apply();
}));
But I get this error message:
"message": "'undefined' is not an object (evaluating '$('#div1').position().left')"
and
dWidth is null
I have sinon spy, but not sure how to use it here. Can I use it somehow to spy on it, or is there a better way to test this method? I think I need to mock "$('#div1')", but no idea how to do that. Any help is highly appreciated.

First, there is typo in your code, you're using dWidth wrong in css function like this adjWdth.
You can create a spy on $ / jQuery function like this and spy for css function
var cssSpy = jasmine.createSpy('cssSpy');
$ = jasmine.createSpy().andCallFake(function(){
return {
width:function(){return 200;},
position: function(){return{left:500};},
css: cssSpy
};
});
$scope.onWindowResize();
expect(cssSpy).toHaveBeenCalledWith({'width':200});
expect(cssSpy).toHaveBeenCalledWith({'left':500});
As you can see, I'm overwriting $ / jQuery function itself and returning an object with the properties/methods you use in the onWindowResize function. And also a spy for css function. andCallFake is old version of jasmine the new one is .and.callFake(). Please confirm if it works for you and study it well. You can do it in beforeEach block to be reusable and mock all keys you want to return when you selecting a DOM element ;). By the way, it's not recommended to have a DOM manipulation code inside controller, it should be in a directive's link function.

Related

Jamsine throws error despite same code working in browser

I added this code to my component controller to focus an input and it worked great in the browser but it broke all of my template tests. I thought I could just flush the $timeout and all would be well but it's not.
vm.$onInit = init;
function init(){
focusInput();
}
function focusInput(){
$timeout(function(){
$document[0]
.querySelector('md-autocomplete-wrap')
.querySelector('input')
.focus();
}, 0);
}
However, in my unit test Jasmine is reporting that .querySelector is not available because the result of the first querySelector is null in the test environment.
it('should render', function(){
var wrap, searchBarDirective, $scope;
$scope = $rootScope.$new();
searchBarDirective = $compile(angular.element(template))($scope);
$scope.$digest();
$timeout.flush();
wrap = searchBarDirective.find('md-autocomplete-wrap')[0];
expect(wrap).toBeDefined();
});
It's obvious to me that $document doesn't contain the rendered directive and thus the second querySelector fails. But why doesn't $document contain the directive?
I tried mocking querySelector with spyOn($document[0], "querySelector").and.returnValues($document[0],$document[0]) but that doesn't get me past the focus. Thinking I have lot my way here.
* Revised *
I think that it is important to continue to use $document but I decided to drop the querySelector for the jqLite find method.
function focusInput(){
$timeout(function(){
var input;
try {
// can throw an error if the first find fails
input = $document.find('md-autocomplete').find('input');
}
catch (e) {
angular.noop(e);
}
if(input && angular.isFunction(input.focus)) {
input.focus();
}
}, 0);
}
The test I changed per comments to below. I do have Karma load jquery to make testing easier which allows me to search for :focus
beforeEach(function(){
element = angular.element(template);
$document[0].body.appendChild(element[0]);
$scope = $rootScope.$new();
});
afterEach(function(){
element[0].remove();
});
it('should be focused', function(){
var input, searchBarDirective;
searchBarDirective = $compile(element)($scope);
$scope.$digest();
$timeout.flush();
input = searchBarDirective.find(':focus')[0];
expect(input).toBeDefined();
});
The reason why your querySelector call works in the browser, but not in tests is that you are creating a DOM element with angular.element, but you are never attaching it to the document. There are two ways to address this:
First, you could simply do this. Instead of:
searchBarDirective = $compile(angular.element(template))($scope);
Do:
let element; // declare this in the describe block so it is available later
element = angular.element(template);
document.body.appendChild(element[0]);
searchBarDirective = $compile(element)($scope);
And then do this:
afterEach(() => element[0].remove());
But, that's a bit messy. You should not be manipulating global scope (ie- the document) in your unit tests unless you have to. It would be better in your non-test code to avoid accessing the document and instead access a scope element, or some other DOM element that you can also mock in your tests. This will be a bit harder to do since it may require re-architecting your code a bit. In general though, in order to make modular and testable code, you want to avoid accessing the document object as much as possible.

Unit testing angular.extend function with jasmine spyOn

I have a directive, where, in certain case I use
angular.extend(dist, src)
Now I would like to test this case and check, if angular.extend is called.
I'm trying to use spyOn
spyOn(angular, 'extend')
And then in test
expect(angular.extend).toHaveBeenCalled()
Not sure I can do it at all, but I decided to give it a try.
Thanks for any help.
EDIT:
Here is my test, edited in accordance with your advise.
it('should create new scope and extend config if config is passed to directive', function() {
var spy = jasmine.createSpy('extendSpy').and.callThrough();
angular.extend = spy;
timeout.flush();
_.forEach(scope.accordionConfig, function(configItem) {
if (configItem.config) {
expect(angular.extend).toHaveBeenCalled();
}
});
});
In beforeEach hook I don't have anything special, just assigning config, creating some other preparation for rest tests and compiling the directive.
Here is a snippet from link function which I'm trying to test
if (scope.format === 'directive') {
if (scope.config) {
newScope = $rootScope.$new();
angular.extend(newScope, scope.config);
}
scope.content = $compile(scope.content)(newScope || scope);
}
console log the value of angular.extend before the assertion, it should be an instance of jasmine.spy, if it is not, there is a problem in the way that you create the spy, and we will need more context, perhaps your full code could help.
I assume you create the hook somewhere in the beforeEach hook or on one of the other hooks?
Try the following code:
var spy = jasmine.createSpy('extendSpy').and.callThrough();
angular.extend = spy;
expect(spy).toBeCalledWith(dist, src);

How to mock config-phase providers for unit tests?

I'm writing a spec that checks a method is called during the config phase of the Angular module under test.
Here's a simplified look at the code being tested:
angular.module('core',['services.configAction'])
.config(function(configAction){
configAction.deferIntercept(true);
});
What happens above is we define a core module that has a single dependency.
Then, in the config-block of the core module, we call the deferIntercept method on the configAction object given to use from services.configAction.
I'm trying to test that core's config calls that method.
This is the current setup:
describe('core',function()
{
const configActionProvider={
deferIntercept:jasmine.createSpy('deferIntercept'),
$get:function(){
return {/*...*/}
}
};
beforeEach(function()
{
module(function($provide)
{
$provide.provider('configAction',configActionProvider);
});
module('core.AppInitializer');
inject(function($injector)
{
//...
});
});
it('should call deferIntercept',function()
{
expect(configActionProvider.deferIntercept).toHaveBeenCalledWith(true);
});
});
The problem with that is that it doesn't override configAction and so the spy is never called, the original method is.
It will do so if I remove it as a dependency of the core module, so angular.module('core',[]) instead of angular.module('core',['services.configAction']) will work and the spy is called.
Any idea how to override services.configAction during testing without removing it from the dependency list?
Have a look at - https://dzone.com/articles/unit-testing-config-and-run.
Something like the following -
module('services.configAction', function (configAction) {
mockConfigAction = configAction;
spyOn(mockConfigAction, 'deferIntercept').andCallThrough();
});
module('core');
in your beforeEach might do the job.

Cannot get the scope os a controller in anugular.js

I am trying to test a javascript file which has in it a controller and some HTML DOM elements which it interacts with.
The class under test is:
function BaseConceptMenu(options) {
var baseConceptMenu = new BaseMenu(options);
//Public function -->Method under Test
function showCodeConceptToolbar() {
var scope = angular.element('#toolbar').scope();
scope.$apply(function() {
scope.toolbarController.show(baseConceptMenu.someObject);
});
}
I am trying to mock the controller and create the HTML DOM element "toolbar" on the fly without trying to create an external HTML template just for the sake of testing.
I am trying to create the div "toolbar" inside the before each and mocking the "CodeConceptToolbarController" controller
beforeEach(inject(function ($rootScope, $compile) {
elm = document.createElement('div');
elm.id = 'toolbar';
scope = $rootScope.$new();
createController = function() {
return $controller('CodeConceptToolbarController', {
$scope: scope
});
};
$compile(elm)(scope);
scope.$digest();
}));
However when I try to test it as below
it('Test Code ConeptToolbarContoller', function() {
// var toolbar = angular.element('#toolbar');
document.getElementById("toolbar").scope().toolbarController = createController();
//As of now,not doing any-idepth testing
//Just a base test call
var menu = new BaseConceptMenu({});
expect(menu.show()).toBe(true);
});
I get this error
TypeError: Cannot read property 'scope' of null
Could anyone provide a way to test this?
or is there a better way to test this?
currently I am using Maven-jasmine plugin
Two problems:
As per https://developer.mozilla.org/en-US/docs/Web/API/document.getElementById, "Elements not in the document are not searched by getElementById." $compile doesn't insert the element into the DOM - it just sets up appropriate bindings (and does smart things like handling nested directives inside your template string). Your getElementById will fail to find a match. You need to insert the element into the DOM somewhere.
getElementById returns a raw HTML DOM element. To get the Angular scope from it, the docs call for wrapping it in angular.element():
var element = document.getElementById(id);
angular.element(element).scope()...
This pattern will provide the Angular wrapper around the element to do the rest of the magic. It's all based on jqLite, which isn't jQuery but does follow a lot of the same patterns. For those used to jQuery, think of it like writing $(element).

Checking Backbone view method called with Jasmine

I have a Backbone application I'm working on where all the functionality is namespaced under App. I have a view called App.LoginView, and I want to test it with Jasmine, but I'm struggling to get it to work.
This is the code for the test:
describe("Test the views", function () {
'use strict';
beforeEach(function () {
// Create DOM element
$('body').append('<div class="content"></div>');
});
afterEach(function () {
$('div.content').remove();
});
// Test the LoginView object was created
it("tests that the LoginView object exists", function () {
// Check the view definition exists
expect(App.LoginView).toBeDefined();
// Spy on the prototype
spyOn(App, 'LoginView').andCallThrough();
// Create the view
this.LoginView = new App.LoginView();
// Check the view exists
expect(this.LoginView).toBeDefined();
expect(this.LoginView.initialize).toBeDefined();
expect(this.LoginView.template).toBeDefined();
expect(this.LoginView.tagName).toBeDefined();
expect(this.LoginView.render).toBeDefined();
// Remove it
this.LoginView.remove();
});
});
It throws the following error:
TypeError: 'undefined' is not a function (evaluating 'spyOn(App, 'LoginView').andCallThrough()')
I'm using the grunt-contrib-jasmine to run the tests and jasmine-jquery to add support for jQuery. I have used Jasmine a little bit in the past, but I'm struggling to see where I've gone awry here.
Found a solution in the end. To get it working, I spied on the initialize method of the prototype and changed the syntax for the call through as this changed in Jasmine 2.
spyOn(App.LoginView.prototype, 'initialize').and.callThrough();
I also dynamically inserted a basic template into the DOM using jasmine-jquery as otherwise Underscore complained about the missing template:
$('body').append('<script type="text/template" id="login-template"><div class="content-padded"></div></script>');

Resources