Checking Backbone view method called with Jasmine - backbone.js

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

Related

Testing angularjs es6 factories with jasmine

I have the following file: classAModel.js with the following code:
class classAModel{
constructor(model) {
'ngInject';
if (!model) return {};
this.id = model.id;
this.name = model.name;
this.displayName = model.displayName;
}
}
export default classAModel;
This code is defined as a factory in another file: module.js:
import classAModelfrom './classAModel'
module.factory('ClassAModel', ClassAModel);
This code works perfectly when not in a testing context. It works using Webpack to create a bundle that is loaded and runs. So far so good. Now, the question is how do I test this class. Before I changed my code to es6 style, it used be a function and it worked. The test first loads the bundle, but when I try to inject the factory (again, same as before), I get an error: Unknown provider: modelProvider <- model <- classAModel. I can understand why he thinks there is a problem, but I can't understand how to fix it.
Moreover, I'm wondering if this is the correct way to work with the factory rather then create a factory method inside the class, that gets the model, and then create my object.
Thanks
Based on the information you've provided, here's a simple test case for testing your factory. Hope this is what you're looking for.
import classAModel from './classAModel'
let classAInstance;
describe('classAModel', function() {
beforeEach(function() {
modelInstance = new Model();
classAInstance = new classAModel(modelInstance);
});
it('should have id provided by model', () => {
expect(classAInstance.id).toBe(modelInstance.id);
});
});

Jasmine test for method containing jQuery

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.

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.

Angular Testing DOM after update by Factory

Ok, I'm trying to test the outcome of a function that updates the DOM>
I have a directive that loads a template via url.
Then a controller calls a factory method to update the html table with data.
I have the tests showing that I can get the data that is all good.
but how can I test that the updates to the table have taken place?
I am using NodeJS with Karma and Jasmine.
I have followed tutorials on how to load in templates, and I have that working, I can load and access the templates in my test fine.
but when I run the method to update the table, the tests fail.
I'll give an scaled down example of what I'm trying to do. Note, this is just demo code, Not a working app.
Template.
<table><tr><td class="cell1"></td></tr></table>
Directive.
dataTable.directive('dataTable', function () {
return {
restrict: 'E',
templateUrl: 'path/to/template/dataTable.html'
};
});
Controller
dataTable.controller('dataTableController', ['$scope', 'dataTableFactory',
function ($scope, dataTableFactory){
$scope.updateTable = function(){
dataTableFactory.loadData();
// code to load data from dataTableFactory here! //
dataTableFactory.updateTable();
}
}])
Factory
dataTable.factory('dataTableFactory',['$document',function($document){
var _tableData;
return(
"tableData": _tableData,
loadData: function(){
// code to get data and populate _tableData.
}
updateTable: function(){
$document.find('.cell1').append(this.tableData.data);
}
)
}])
Unit Test
describe('dataTable Tests', function () {
var scope, element, $compile, mDataTableFactory, controller, tableData, doc, factory;
beforeEach(module('dataTable'));
beforeEach(module('app.templates')); // setup via ng-html2js
beforeEach(inject(function (_$rootScope_, _$compile_,_$controller_,_dataTableFactory_) {
scope = _$rootScope_.$new();
doc = _$compile_('<flood-guidance></flood-guidance>')(scope);
factory = _dataTableFactory_;
controller = _$controller_('dataTableController', {
$scope: scope,
$element: doc,
dataTableFactory: factory
});
scope.$digest();
}));
it("Template should contain the cell cell1", function(){
expect(doc.find('.cell1').contents().length).toBe(0);
expect(doc.find('.cell1').html()).toBeDefined();
});
// Passes fine, so I know the template loads ok.
it('Should show data in cell1',function(){
factory.tableData = {data: 'someData'};
scope.updateTable();
expect(doc.find('.cell1').contents().length).toBe(1);
expect(doc.find('.cell1').html()).toBe('SomeData');
});
});
});
Test Ouptut
Expected 0 to be 1. Expected '' to be 'someData'.
If I put the updateTable code in to the controller and call the update function there, the test passes, but I'd like to have this in a factory, how can I make this test pass (the app runs and works as expected, I just can't get a working test).
I understand this kind of testing is more focused on the UI and not exactly 'Unit Testing' but is it possible to do this?
So essentially updateTable cannot find the changes performed by factory.tableData. I guess the problem may be due to the way how your factory exposes the _tableData property.
Could you try to modify your factory like this:
dataTable.factory('dataTableFactory',['$document',function($document){
var _tableData;
return(
getTableData: function() { return _tableData; },
setTableData: function(newVal) { _tableData = newVal; },
loadData: function(){
// code to get data and populate _tableData.
}
updateTable: function(){
$document.find('.cell1').append(this.tableData.data);
}
)
}])
and then of course use the setter/getter accordingly. See if it works this way.
OK so I'm still not sure if I fully get your intention but here is a fiddle with my refactored example.
http://jsfiddle.net/ene4jebb/1/
First of all the factory shouldn't touch the DOM, that's the directives responsibility. Thus my rework passes the cellvalue (new scope property) to the directive, which renders it. Now when you call setTableData (which will change _tableData.data) and since in test environment call the $digest loop yourself, the directive will automatically redraw the new stuff.
Controller is kept thin as possible thus only providing a scope property to the factory.
As said not sure if you were after this, but hope it helps. If there are any questions just ask.

jquery jasmine, fixtures not loaded

I'm trying to load my fixtures in spec/category_keyword/categories.html. The content of the fixture is
My test
$(function() {
beforeEach(function(){
keywordsListView = new KeywordsListView({
el: $("#keywords_list")
})
})
it("should ...", function() {
expect(keywordsListView.el.attr("id")).toEqual("keywords_list");
})
})
However, keywordsListView.el is undefined which suggests to me that the fixture is somehow not loaded. I viewed the jasmine suite in firebug and didn't see anything related to fixture. I'm using the jasmine gem. Do I have to enable something?
Looking at this, I don't think the test has access to your keywordsListView variable. Declare the keywordsListView variable above the beforeEach function and populate it in the beforeEach as you currently have it.
$(function() {
var keywordsListView;
beforeEach(function(){
keywordsListView = new KeywordsListView({
el: $("#keywords_list")
})
})
it("should ...", function() {
expect(keywordsListView.el.attr("id")).toEqual("keywords_list");
})
})
Jasmine itself doesn't include any fixture loading, the html reporter gives you a div#jasmine_content that jasmine will not touch and is yours to write html to if needed. If you need to be able to load more complicated fixtures, there are other plugins that will do that for you. The only one I've ever used is jasmine-jquery. With this you can do something like:
$(function() {
beforeEach(function(){
loadFixtures('category_keyword/categories.html');
keywordsListView = new KeywordsListView({
el: $("#keywords_list")
});
});
it("should ...", function() {
expect(keywordsListView.el.attr("id")).toEqual("keywords_list");
});
});
Note that jasmine-jquery expects your fixtures to be in spec/javascripts/fixtures unless otherwise configured by setting jasmine.getFixtures().fixturesPath = <something>
The other thing you can do is let backbone create the element for you by not passing one in. Of course for this, your view will need to render itself instead of relying on whatever markup is generated by the server. This makes your view more self-contained as well.
After loading your fixtures, you have to call your backbone functions / method without making spies on for your models.
And then you have find with your loaded HTML content.
Like:
loadFixtures("/path/to/fixtures.html");
SpyOnObj.functionname();
expect($('body')).toHaveId("{ELEMENT_ID}");
I hope this will helps to you.

Resources