mock a function call using jasmine - javascript

Getting started with HotTowelAngular template and I'm setting up unit testing. Ran into a snag.
I'm trying to test a function in my controller that happens to call another function named "log". This "log" is a function stored in a private variable which gets its value from a dependency called "common".
I'm aware that I probably need to stub this function in some way, but I'm unsure of where to start for this particular example, as I'm pretty new to angularjs, jasmine, et all. Any ideas are appreciated
unit test:
describe("quote", function () {
var scope,
controller,
common;
beforeEach(inject(function($rootScope, $controller, _common_) {
scope = $rootScope.$new();
common = _common_;
controller = $controller;
}));
describe("removeAttachment", function() {
it("should remove the attachment when requested", function() {
var vm = controller("quote", { $scope: scope });
vm.attachmentList.push({ FileName: "file1", FileAsString: "..." });
vm.attachmentList.push({ FileName: "file2", FileAsString: "..." });
vm.attachmentList.push({ FileName: "file3", FileAsString: "..." });
expect(vm.attachmentList.length).toEqual(3);
// here's the call where it fails
vm.removeAttachment(vm.attachmentList[1]);
expect(vm.attachmentListCount).toEqual(2);
expect(vm.attachmentList.length).toEqual(2);
expect(vm.attachmentList[1].FileName).toBe("file3");
});
});
});
controller:
var getLogFn = common.logger.getLogFn;
var log = getLogFn(controllerId);
function removeAttachment(attachment) {
// need to stub this out
log('removing attachment: ' + attachment.FileName);
vm.attachmentList.splice(vm.attachmentList.indexOf(attachment), 1);
vm.attachmentListCount = vm.attachmentList.length;
}
The error from Jasmine:
TypeError: 'undefined' is not a function (evaluating 'log('removing attachment: ' + attachment.FileName)')

In your test you should have controller = $contoller("YourController", {it's dependencies});
You probably don't want to pass in your common service, but create a stub that returns a function.
var wrapper = {logger: function () {}};
var stub = { logger: { getLogFun: function() {return wrapper.logger} }};
You can pass that in place of your common service.
You can now spy on it with:
spyOn(wrapper, 'logger');

Is var log = getLogFn(controllerId) being called before removeAttachment? Is getLogFn() injected into the controller? If these are both true, you could stub out getLogFn() to return a dummy log object that you could use for testing. I am not aware of hottowel, I use Sinon. In Sinon, I would call
var getLogFnStub = sinon.stub().returns(function (msg) { return 1;/*My expected result*/ });
and pass that into your controller, like
var vm = controller("quote", { $scope: scope, getLogFn: getLogFnStub});
If you wanted, you could then do asserts against the stub you made in Sinon, like so
sinon.assert.called(getLogFnStub);

Related

Object is not defined when stubbing with Jasmine

I am very new to Jasmine. I am intending to use it for with vanilla javascript project. The initial configuration was a breeze but I am receiving object not defined error while using spyOn.
I have downloaded the version 3.4.0 Jasmine Release Page and added the files 'as is' to my project. I then have changed jasmine.json file accordingly and see the all the example tests passing. However when try spyOn on a private object, I am getting undefined error,
if (typeof (DCA) == 'undefined') {
DCA = {
__namespace: true
};
}
DCA.Audit = {
//this function needs to be tested
callAuditLogAction: function (parameters) {
//Get an error saying D365 is not defined
D365.API.ExecuteAction("bu_AuditReadAccess", parameters,
function (result) { },
function (error) {
if (error != undefined && error.message != undefined) {
D365.Utility.alertDialog('An error occurred while trying to execute the Action. The response from server is:\n' + error.message);
}
}
);
}
}
and my spec class
describe('Audit', function(){
var audit;
beforeEach(function(){
audit = DCA.Audit;
})
describe('When calling Audit log function', function(){
beforeEach(function(){
})
it('Should call Execute Action', function(){
var D365 = {
API : {
ExecuteAction : function(){
console.log('called');
}
}
}
// expectation is console log with say hello
spyOn(D365.API, 'ExecuteAction').and.callFake(() => console.log('hello'));
var params = audit.constructActionParameters("logicalName", "someId", 'someId');
audit.callAuditLogAction(params);
})
})
})
As you can see my spec class does not know about actual D365 object. I was hoping to stub the D365 object without having to inject it. Do I need to stub out whole 365 library and link it to my test runner html?
I got it working after some pondering. So the library containing D365 should still need to be added to my test runner html file. after that I can fake the method call like below,
it('Should call Execute Action', function(){
spyOn(D365.API, 'ExecuteAction').and.callFake(() => console.log('hello'));
var params = audit.constructActionParameters("logicalName", "someId", 'someId');
audit.callAuditLogAction(params);
})
it is now working.

One specific stub in sinon tests not working

I have a problem and can't seem to find the solution.
I'm writing Node.js code and using Sinon to unittest. I am testing a service and stubbing calls to the repository/database. Everything works despite for one repository method where the actual method is invoked instead of its stub. Here's the relevant code.
listingServiceTest.js
var chai = require('chai');
var expect = chai.expect;
var sinon = require('sinon');
var listingService = require('../listingService');
var listingRepository = require('../../repository/listingRepository');
var Listing = require('../../models/interfaceModel/listing');
//some testdata initialization
[..]
describe('saveListing(listing)', function() {
//everything works fine in here
[...]
});
describe('saveTags(listingID, tags)', function() {
beforeEach(function() {
sinon.stub(listingRepository, 'findTags', function(tags, callback) {
//This one works fine
var tags = [{
text: tags[0],
tagID: tags[0] + 'ID'
}];
setTimeout(function () {
callback (tags);
}, 10);
});
sinon.stub(listingRepository, 'saveTagCorrelation', function(listingID, tagID, done) {
setTimeout(function() {
//Here The actual Method is called instead of the stub ?!?!?
console.log('im saveTagCorrelation stub');
done();
}, 10);
});
sinon.stub(listingRepository, 'saveTag', function(tag, callback) {
//This one works fine
});
});
afterEach(function() {
listingRepository.findTags.restore();
listingRepository.saveTag.restore();
listingRepository.saveTagCorrelation.restore();
});
it("saveTags should execute this callback", function() {
listingService.saveTags(util.slugify(pTitle), pTags, function() {
//This obviously fails since the the stub isn't called
expect(listingRepository.saveTagCorrelation.getCall(0).args[0]).to.equal(util.slugify(pTitle));
expect(listingRepository.saveTagCorrelation.getCall(0).args[1]).to.equal(pTags[0] + 'ID');
expect(listingRepository.saveTag.getCall(0).args[0]).to.equal(pTags[1]);
expect(listingRepository.saveTagCorrelation.getCall(1).args[0]).to.equal(util.slugify(pTitle));
expect(listingRepository.saveTagCorrelation.getCall(1).args[1]).to.equal(pTags[1] + 'ID');
});
});
});
And here's the repo, that is supposed to be stubbed (I can see in the logs, that the actual method is logging, instead of the stub).
listingRepository.js:
var db = require ('../models/index');
var util = require ('../util');
module.exports = {
saveListing: [...],
findTags: [...],
saveTag: [...],
saveTagCorrelation: function (listingID, tagID){
//This is called instead of the stub
console.log('in actual Method');
var tagToSave;
tagToSave = db.tag_listing.build({
listingID: listingID,
tagID: tagID
});
tagToSave.save();
},
getListingByID: [...]
}
Am I blind? What am I missing?

Connect to SignalR Hub after connection start

Let's say i have two or more hubs in my server application. My javascipt client (Angular SPA) initialy needs a connection to the first hub, and needs to subscribe to a method like this:
connection = $.hubConnection(appSettings.serverPath);
firstHubProxy = connection.createHubProxy('firstHub');
firstHubProxy('eventFromFirstHub', function () {
console.log('Method invokation from FirstHub');
});
connection.start().done(function (data) {
console.log("hub started");
});
Everything is working fine. Now a user of my Angular SPA may decide to put a widget on his page, which needs to subcribe to a method from the second hub:
secondHubProxy = connection.createHubProxy('firstHub');
secondHubProxy('eventFromSecondHub', function () {
console.log('Method invokation from SecondHub');
});
The method from the second hub is not working. I guess because it was created after connection.start().
My example is simplified, in my real appplication there will be 20+ hubs to which users may or may not subscribe by adding or removing widgets to their page.
As far as i can tell i have two options:
call connection.stop() and then connection.start(). Now both hub subscriptions are working. This just doesn't feel right, because on all hubs, the OnConnected() event fires, and my application will be starting and stopping all the time.
create hub proxy objects for all possible hubs, subscribe to a dummy
method on all possible hubs, so the application can subscibe to hub
methods later if desired. This also doesn't feel right, because i
need to create 20+ hub proxies, while i may need just a few of
those.
Is anybody aware of a pattern which i can use to accomplish this? Or am i missing something very simple here?
Personally I use #2. I have a hub service that subscribes to all client methods. Any of my other angular components then pull that hub service in and subscribe to its events as needed.
Here it is;
hub.js
(function () {
'use strict';
angular
.module('app')
.factory('hub', hub);
hub.$inject = ['$timeout'];
function hub($timeout) {
var connection = $.connection.myHubName;
var service = {
connect: connect,
server: connection.server,
states: { connecting: 0, connected: 1, reconnecting: 2, na: 3, disconnected: 4 },
state: 4
};
service = angular.extend(service, OnNotify());
activate();
return service;
function activate() {
connection.client.start = function (something) {
service.notify("start", something);
}
connection.client.anotherMethod = function (p) {
service.notify("anotherMethod", p);
}
// etc for all client methods
$.connection.hub.stateChanged(function (change) {
$timeout(function () { service.state = change.newState; });
if (change.state != service.states.connected) service.notify("disconnected");
console.log("con:", _.invert(service.states)[change.oldState], ">", _.invert(service.states)[change.newState]);
});
connect();
}
function connect() {
$.connection.hub.start({ transport: 'auto' });
}
}
})();
OnNotify
var OnNotify = function () {
var callbacks = {};
return {
on: on,
notify: notify
};
function on(name, callback) {
if (!callbacks[name])
callbacks[name] = [];
callbacks[name].push(callback);
};
function notify(name, param) {
angular.forEach(callbacks[name], function (callback) {
callback(param);
});
};
}
Then I can subscribe to things as needed, for example in a controller;
(function () {
'use strict';
angular
.module('app')
.controller('MyController', MyController);
MyController.$inject = ['hub'];
function MyController(hub) {
/* jshint validthis:true */
var vm = this;
vm.something = {};
hub.on('start', function (something) {
$timeout(function () {
console.log(something);
vm.something = something;
});
});
}
})();

Karma testing a lot of files similar in structure automatically

So I have a folder full of scripts that all resemble a structure like this
// Adapter-100.js
angular.module('myModule', ['myParentFactory', function(myParentFactory) {
return angular.extend(myParentFactory, {
"someFunctionA" : function() {},
"someFunctionB" : function() {},
"someFunctionC" : function() {}
});
}]);
And my test just checks that they have these three methods, trouble is there is about 100 of these files (they're adapters for communicating with a server)
Here is a representation of my tests file
// api-adapter-tests.js
describe('Unit: EndPointMethods', function() {
var values, factory, adapter;
// Boot the module
beforeEach(function() {
module('myModule');
inject(function ($injector) {
values = $injector.get('AppConsts');
factory = $injector.get('EndPointConnection');
adapter = $injector.get('TestAdapter'); // This needs to change to match what adapter is being tested
});
});
// Run some tests
describe('AppConsts', function() {
it('Should have an api_host key', function() {
expect(values).toBeDefined();
expect(values.api_host).toBeDefined();
expect(typeof values.api_host).toBe('string');
});
});
// Is this able to be generated to test each adapter independently?
describe('TestEndPointMethod has minimum functional definitions', function() {
it('should have 3 defined functions', function() {
expect(factory.consumeResponse).toBeDefined();
expect(factory.getEndPoint).toBeDefined();
expect(factory.mutator).toBeDefined();
});
});
});
I don't want to have to write a separate describes/it block for each adapter but rather have Karma loop over all of these and create the tests on the fly (the tests are very unlikely to ever change)
I've Googled around for a solution to this but can't seem to find anything that does this kind of thing for me.
You can wrap your suites in a clojure and pass the Adapter you want to test: mocha will take care of running it in the right way - and so Karma.
function runSuiteFor(newAdapter){
return function(){
// api-adapter-tests.js
describe('Unit: EndPointMethods', function() {
var values, factory, adapter;
// Boot the module
beforeEach(function() {
module('myModule');
inject(function ($injector) {
values = $injector.get('AppConsts');
factory = $injector.get('EndPointConnection');
adapter = $injector.get(newAdapter); // set the Adapter here
});
});
// Run some tests
describe('AppConsts', function() {
it('Should have an api_host key', function() {
expect(values).toBeDefined();
expect(values.api_host).toBeDefined();
expect(typeof values.api_host).toBe('string');
});
});
// Is this able to be generated to test each adapter independently?
describe('TestEndPointMethod has minimum functional definitions', function() {
it('should have 3 defined functions', function() {
expect(factory.consumeResponse).toBeDefined();
expect(factory.getEndPoint).toBeDefined();
expect(factory.mutator).toBeDefined();
});
});
});
}
}
var adapters = ['MyTestAdapter1', 'MyTestAdapter2', etc...];
for( var i=0; i<adapters.length; i++){
runSuiteFor(adapters[i])();
}
Note: IE8 has some issues with this approach sometimes, so in case you're with Angular 1.2 bare in mind this.

Jasmine testing multiple spies

I'm writing a few tests for an Angular application, these are my first stab at unit tests for Angular using Jasmine. I'm having trouble structuring the test to cater for the various scenarios inside the function (namely the if statement and callbacks).
Here's my $scope function, which takes an Object as an argument, and if that object has an id, then it updates the object (as it'll already exist), otherwise it creates a new report and pushes to the backend using the CRUD service.
$scope.saveReport = function (report) {
if (report.id) {
CRUD.update(report, function (data) {
Notify.success($scope, 'Report updated!');
});
} else {
CRUD.create(report, function (data) {
$scope.report = data;
Notify.success($scope, 'Report successfully created!');
});
}
};
My test so far passes in a fake Object with an id so it'll trigger the CRUD.update method, which I then check is called.
describe('$scope.saveReport', function () {
var reports, testReport;
beforeEach(function () {
testReport = {
"id": "123456789",
"name": "test"
};
spyOn(CRUD, 'update');
$scope.saveReport(testReport);
});
it('should call CRUD factory and update', function () {
expect(CRUD.update).toHaveBeenCalledWith(testReport, jasmine.any(Function));
});
});
I understand Jasmine doesn't allow multiple spies, but I want to be able to somehow test for the if condition, and run a mock test for when the Object doesn't pass in an Object too:
describe('$scope.saveReport', function () {
var reports, testReport;
beforeEach(function () {
testReport = {
"id": "123456789",
"name": "test"
};
testReportNoId = {
"name": "test"
};
spyOn(CRUD, 'update');
spyOn(CRUD, 'create'); // TEST FOR CREATE (NoId)
spyOn(Notify, 'success');
$scope.saveReport(testReport);
$scope.saveReport(testReportNoId); // TEST FOR NO ID
});
it('should call CRUD factory and update', function () {
expect(CRUD.update).toHaveBeenCalledWith(testReport, jasmine.any(Function));
// UNSURE ON THIS PART TOO
});
});
I've read things about using the .andCallFake() method, but I could not see how this could work with my setup. Any help really appreciated.
It seems that you should decide on what you need to test first. If you want to test simply that update is called when id exists or create is called when it does not then you should just structure the it function with those conditions. The before each is the wrong place for some of those things.
it('should call CRUD factory and update', function () {
spyOn(CRUD, 'update');
$scope.saveReport(testReport);
expect(CRUD.update).toHaveBeenCalledWith(testReport, jasmine.any(Function));
});
it('should call CRUD create', function() {
spyOn(CRUD, 'create');
$scope.saveReport(testReportNoId); // TEST FOR NO ID
expect(CRUD.create).toHaveBeenCalledWith(testReport, jasmine.any(Function));
});
Only put things in the before each that you actually should do before each test.
Hope this helped!

Categories

Resources