jquery jasmine, fixtures not loaded - backbone.js

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.

Related

Unit Testing Component in AngularJS 1.5 and Typescript

Working on converting some old broken AngularJS unit tests to TypeScript and came across this error:
Error: [$injector:unpr] Unknown provider: myComponentDirectiveProvider <- myComponentDirective
In every example of AngularJS unit testing I've seen, they have a main module with everything on it, and they inject that main module into the unit test with angular.mock.module('theWholeStinkingApp'). That is nice for a little tutorial, but I'm dealing with a very large application with hundreds of components and dozens of services. Not to mention filters and other directives. It doesn't work to just inject the whole app into a unit test. Nor do I think it's a good idea to start up the whole app just to unit test a component (kind of defeats the purpose of unit testing when you have to include everything you own in every test).
For testing services I can just create my module in the test in a beforeEach like this, so I'm mocking dependencies, but injecting the real service I want to test:
angular.mock.module(($provide) => {
$provide.service('firstDependency', FirstDependencyMock);
$provide.service('secondDependency', SecondDependencyMock);
$provide.service('serviceIWantToTest', ServiceIWantToTest);
});
I can't figure out how to do this and inject a Component. Any help would be appreciated.
Please keep in mind, I do not want to have to angular.mock.module('theWholeApp') to get this working. I just want to create a mock module and attach my component to it.
Here's a slimmed down version of what I'm doing.
Component looks something like this
angular.module('theWholeApp', [])
.component('myComponent', {
controller: MyComponentController, // class is defined elsewhere
templateUrl: 'path/to/my/template.html'
)
... // 100+ other components;
Here's the test:
describe('MyComponent', () => {
beforeEach(() => {
// angular.mock.module('theWholeApp'); This is not an option.
// Creating a mock module with just the dependencies I need for this test
angular.mock.module(($provide) => {
$provide.service('firstDependency', FirstDependencyMock);
$provide.service('secondDependency', SecondDependencyMock);
});
// Tried adding this to register the controller, but it doesn't help.
angular.mock.module(($controllerProvider) => {
$controllerProvider.register('MyComponentController', MyComponentController);
});
angular.mock.inject(($injector) => {
$rootScope = $injector.get('$rootScope');
$componentController = $injector.get('$componentController');
firstDependency= $injector.get('firstDependency');
secondDependency= $injector.get('secondDependency');
$scope = $rootScope.$new();
});
});
describe('doSomething', () => {
it('exists', () => {
controller = $componentController('myComponent', {$scope});
expect(controller.doSomething).toBeDefined();
});
});
});
Obviously, this isn't the live code, just a representation. Hopefully I got all the made up names right. The point is, I want to create a mock module and add my component to it so a call to $componentController('myComponent', {$scope}) works.
Thanks!
Alright, the answer I came up with is deceptively simple, but I've never seen it done anywhere. If anyone can tell me why it's a bad idea and suggest something better, I'm all for it.
The answer is to just create the module you want then immediately add it to the test.
describe('MyComponent', () => {
// Create a new module with the things you need.
// Putting it outside the "beforeEach" because it only needs to be created once.
angular.module('myComponentTestModule', [])
.component('myComponent', {
controller: MyComponentController, // class is defined elsewhere
templateUrl: 'path/to/my/template.html'
)
.service('firstDependency', FirstDependencyMock)
.service('secondDependency', SecondDependencyMock);
beforeEach(() => {
// Add the new module to the test suite
angular.mock.module('myComponentTestModule');
angular.mock.inject(($injector) => {
$rootScope = $injector.get('$rootScope');
$componentController = $injector.get('$componentController');
firstDependency= $injector.get('firstDependency');
secondDependency= $injector.get('secondDependency');
$scope = $rootScope.$new();
});
});
describe('doSomething', () => {
it('exists', () => {
controller = $componentController('myComponent', {$scope});
expect(controller.doSomething).toBeDefined();
});
});
});

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.

How to mock service in angularAMD with karma/jasmine?

I have a project using AngularAMD/RequireJS/Karma/Jasmine, that I have the basic configuration all working, most unit tests run and pass successfully.
I cannot get a mocked service injected correctly using either angular.mock.module or angularAMD.value().
I have:
// service definition in services/MyService.js
define(['app'],
function(app) {
app.factory('myService', [ '$document', function($document) {
function add(html) {
$document.find('body').append(html);
}
return { add: add };
}]);
}
);
// test
define(['angularAMD', 'angular-mocks', 'app', 'services/MyService'],
function(aamd, mocks, app) {
describe('MyService', function() {
var myBodyMock = {
append: function() {}
};
var myDocumentMock = {
find: function(sel) {
// this never gets called
console.log('selector: ' + sel);
return myBodyMock;
}
};
var svc;
beforeEach(function() {
// try standard way to mock a service through ng-mock
mocks.module(function($provide) {
$provide.value('$document', myDocumentMock);
});
// hedge my bets - try overriding in aamd as well as ng-mock
aamd.value('$document', myDocumentMock);
});
beforeEach(function() {
aamd.inject(['myService',
function(myService) {
svc = myService;
}]);
});
it('should work', function() {
// use svc expecting it to have injected mock of $document.
spyOn(myDocumentMock, 'find').andCallThrough();
spyOn(myBodyMock, 'append');
svc.add('<p></p>');
expect(myDocumentMock.find).toHaveBeenCalledWith('body');
expect(myBockMock.append).toHaveBeenCalledWith('<p></p>');
});
});
}
);
Does anyone know where I'm going wrong ? Any help would be much appreciated.
Angular isn't asynchronous, I think is not a good ideia use both. If you're trying to reach to a good modularization method, okay, but use the RequireJS optimizer to build everything before you put this on your browser, and about the tests, I think you can just use RequireJS optimizer to build your modules before, it will let you free from "CommonJS environment even in tests".
Looks like it'll be an issue with variable scopes, karma is very finicky about that. I think you should initialize your mock objects globally, then set them in the beforeEach.
The top line of my test files always looks something like:
var bodyMock, svcMock, foo, bar
Then in the beforeEach'es I set the values
Edit: Since bodyMock is only a scope variable, at the point where the tests are actually running and the browser is looking for an object 'bodyMock', it can't find anything.

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

Jasmine: Backbone.history.loadUrl Breaks Unrelated Tests

I've found the only way to navigate to different URLs to do view and router behavior tests is to use Backbone.history.loadUrl(). Backbone.history.navigate('#something', true) and router.navigate('#something, {trigger: true, replace: true} and any combination thereof do not work within the test. My application does NOT use pushstate.
This works correctly within the context of a single test.
describe('that can navigate to something as expected', function(){
beforeEach(function() {
this.server = sinon.fakeServer.create();
//helper method does my responds to fetches, etc. My router in my app is what starts Backbone.history
this.router = initializeBackboneRouter(this.server, this.fixtures, false);
});
afterEach(function(){
this.server.restore();
Backbone.history.stop();
Backbone.history.loadUrl('#');
});
it('should create the view object', function(){
Backbone.history.loadUrl('#something');
expect(this.router.myView).toBeDefined();
});
});
During testing you can see that backbone is appending hashes as expected to the URL: localhost:8888/#something Depending on the test.
Unfortunately, loadUrl seems to be introducing a lot of inconsistencies in the way the tests behave. During one of my tests that involves some asynchronous JS where I need to wait for an AJAX call to complete, the fails about 50% of the time with a timeout or sometimes Expected undefined to be defined. If I console out the data I expect to be there it is, so I know it's not a BS test.
it('should add the rendered html to the body', function(){
runs(function(){
Backbone.history.loadUrl('#something');
});
waitsFor(function(){
var testEl = $('#title');
if(testEl.length > 0){ return true; }
}, 1000, 'UI to be set up');
runs(function(){
var testEl = $('#title');
expect(testEl.text()).toEqual(this.router.model.get(0).title);
});
});
The important note here is that it only fails when all tests are run; run by itself it passes 100% of the time.
My question then is: is Backbone.history.loadUrl a bad way to do programatic navigation around a backbone app in jasmine? I feel like I've tried everything to get this to simulate a user going to a specific URL. Is my teardown incorrect? I've tried it without the Backbone.history.loadUrl('#'); and got different behavior but not passing tests.
The core problem seems to be that in the context of several, hundreds, or even a few jasmine tests, Backbone.history is not clearing itself out and is sticking around as one instance of itself instead of being completely re-initialized at each test.
This sucked.
The solution was to edit my code a bit to add a loading complete flag that was set to true when i was sure that the DOM was 100% finished loading.
Then I wrote a helper function that waited for that flag to be true in my beforeEach function in the root of each test.
var waitForLoadingComplete = function(view){
waitsFor(function(){
if(view.loadingComplete == true){return true;}
}, 100, 'UI Setup Finished');
}
After that I refactored my setup into a helper function:
var setupViewTestEnvironment = function(options) {
var temp = {};
temp.server = sinon.fakeServer.create();
temp.router = initializeBackboneRouter(temp.server, options.fixture);
waitForLoadingComplete(temp.router.initialview);
runs(function(){
Backbone.history.loadUrl(options.url);
temp.view = temp.router[options.view];
temp.model = temp.router[options.model];
waitForLoadingComplete(temp.view);
});
return temp;
}
Example use:
beforeEach(function() {
this.testEnv = setupViewTestEnvironment({
url: '#profile',
view: 'profileIndex',
model: 'myModel',
fixture: this.fixtures
});
});
After which I had a view that i had loaded which I could be assured was finished loading so I could test stuff on the DOM or anything else I wanted to do.

Resources