I'm trying to test one of my functions, but part of it uses a private variable from the controller.
How can I get Jasmine to fake the data from that private variable?
window.MyApp = window.MyApp || {};
(function(myController) {
var deliverablesKoModel;
myController.initialize = function(releaseId) {
// Ajax call with this success:
deliverablesKoModel = new knockOutModel(data); // this model contains an observable array named 'deliverables'
};
myController.checkDeliverableNameIsValid = function (deliverable) {
var valid = false;
if (ko.unwrap(deliverable.name) !== null && ko.unwrap(deliverable.name) !== undefined) {
// PROBLEM HERE
// when running the test deliverablesKoModel below is always undefined!
/////////////
valid = _.all(deliverablesKoModel.deliverables(), function(rel) {
return (ko.unwrap(rel.name).trim().toLowerCase() !== ko.unwrap(deliverable.name).trim().toLowerCase()
|| ko.unwrap(rel.id) === ko.unwrap(deliverable.id));
});
}
deliverable.nameIsValid(valid);
return valid;
};
}(window.MyApp.myController = window.MyApp.myController || {}));
My Jasmine test. I tried having deliverablesKoModel be a global variable but it's always out of scope when hitting the method above.
describe("checkDeliverableNameIsValid should", function () {
var deliverable;
beforeEach(function () {
window['deliverablesKoModel'] = {
deliverables: function() {
return fakeData.DeliverablesViewModel.Deliverables; // this is a json object matching the deliverables in the knockout model
}
};
deliverable = {
id: 1,
name: "test 1",
nameIsValid: function(isValid) {
return isValid;
}
};
});
it("return false if any deliverable already exists with the same name", function () {
var valid = myApp.myController.checkDeliverableNameIsValid(deliverable);
expect(valid).toBe(false);
});
});
deliverablesKoModel is private to code outside of your IIFE.
I am not familiar with knockout, but there are a few ways to set deliverablesKoModel.
Make it a property of your controller that you can set/get.
Make your controller #initialize method accept a callback function which can return an instance of your model. Then, you can send in a function when calling #initialize on your controller in your test.
Example for approach #2 above:
var deliverablesKoModel;
myController.initialize = function(releaseId, modelCallback) {
// Ajax call with this success:
deliverablesKoModel = modelCallback(data); //returns a model
};
Spec:
it("return false if any deliverable already exists with the same name", function () {
var fakeModel = function(data) {
return {
deliverables: function() {
return fakeData.DeliverablesViewModel.Deliverables;
}
}
};
//You didn't initialize your
//controller, which made the "private" variable deliverablesKoModel null in your IIFE
myApp.myController.initialize(relaseId, fakeModel);
var valid = myApp.myController.checkDeliverableNameIsValid(deliverable);
expect(valid).toBe(false);
});
Related
I am spying a JS method. I want to return different things based on actual argument to the method. I tried callFake and tried to access arguments using arguments[0] but it says arguments[0] is undefined.
Here is the code -
spyOn(testService, 'testParam').and.callFake(function() {
var rValue = {};
if(arguments[0].indexOf("foo") !== -1){
return rValue;
}
else{
return {1};
}
})
This is suggested here - Any way to modify Jasmine spies based on arguments?
But it does not work for me.
Use of arguments should work just fine. Also on a side note could you paste your entire object under test- though that's not the source of the issue.
Here is how I used it. See it in action here
var testObj = {
'sample': "This is a sample string",
'methodUnderTest': function(param) {
console.log(param);
return param;
}
};
testObj.methodUnderTest("You'll notice this string on console");
describe('dummy Test Suite', function() {
it('test param passed in', function() {
spyOn(testObj, 'methodUnderTest').and.callFake(function() {
var param = arguments[0];
if (param === 5) {
return "five";
}
return param;
});
var val = testObj.methodUnderTest(5);
expect(val).toEqual('five');
var message = "This string is not printed on console";
val = testObj.methodUnderTest(message);
expect(val).toEqual(message);
});
});
Lets say I have a javascript object with the the following
var Settings = function () {
this.timelimit = 0;
this.locked = false;
this.expires = null;
this.age = null;
};
And then I set some get/set functions like:
Settings.prototype = {
getAllAges: function () {
return self.age;
},
getTimeLimit: function () {
return self.timelimit;
},
load: function() {
data_from_local_storage = LoadLocalStorage();
}
}
In data_from_local_storage I have JSON variables that match the above variables (timelimit, locked etc .. )
Issue is, the object var settings_ref = Settings() have all these 4 variables - but also have these 3 functions assigned in settings_ref - due to this OO behavior I need to write inside the load() function:
this.timelimit = data_from_local_storage.timelimit
this.age = data_from_local_storage.age
this.locked = data_from_local_storage.locked
Because if I'll write
this = data_from_local_storage it will destroy my object.
So how can I avoid writing all these variables one-by-one ?
w/o a for loop inside a function
in this example are just 4 but there are much much more and I cannot write it everywhere everytime
I'm looking for some .update() function like in Python or something ..
Any quick shortcut that someone know ?
You can use Object.assign() in ES2015:
load: function() {
Object.assign(this, LoadLocalStorage());
}
It's apparently not supported yet in IE, but there's a polyfill on the MDN page:
if (typeof Object.assign != 'function') {
(function () {
Object.assign = function (target) {
'use strict';
// We must check against these specific cases.
if (target === undefined || target === null) {
throw new TypeError('Cannot convert undefined or null to object');
}
var output = Object(target);
for (var index = 1; index < arguments.length; index++) {
var source = arguments[index];
if (source !== undefined && source !== null) {
for (var nextKey in source) {
if (source.hasOwnProperty(nextKey)) {
output[nextKey] = source[nextKey];
}
}
}
}
return output;
};
})();
}
(Personally I would use Object.defineProperty() to add the method, but that's verbatim from MDN.)
(edit though I guess if you don't have Object.assign() you may not have Object.defineProperty() either :)
If you store the data inside another object literal, it makes persisting things to localstorage and back a lot easier.. Here is an example..
//pretend local storage loader
function LoadLocalStorage() {
return {
timelimit: 100,
locked: true,
expires: new Date(),
age:40
}
}
var Settings = function () {
this.data = {
timelimit: 0,
locked: false,
expires: null,
age:null
}
};
Settings.prototype = {
getAllAges: function () {
return this.data.age;
},
getTimeLimit: function () {
return this.data.timelimit;
},
load: function() {
this.data = LoadLocalStorage();
}
}
var settings = new Settings;
console.log('Age before our load');
console.log(settings.getAllAges());
settings.load();
console.log('Age after our load');
console.log(settings.getAllAges());
Recently I came across a simple Command pattern implementation in JavaScript that uses function as an object instead of pure object to define functionality:
var CommandManager = (function() {
function CommandManager() {}
CommandManager.executed = [];
CommandManager.unexecuted = [];
CommandManager.execute = function execute(cmd) {
cmd.execute();
CommandManager.executed.push(cmd);
};
CommandManager.undo = function undo() {
var cmd1 = CommandManager.executed.pop();
if (cmd1 !== undefined){
if (cmd1.unexecute !== undefined){
cmd1.unexecute();
}
CommandManager.unexecuted.push(cmd1);
}
};
CommandManager.redo = function redo() {
var cmd2 = CommandManager.unexecuted.pop();
if (cmd2 === undefined){
cmd2 = CommandManager.executed.pop();
CommandManager.executed.push(cmd2);
CommandManager.executed.push(cmd2);
}
if (cmd2 !== undefined){
cmd2.execute();
CommandManager.executed.push(cmd2);
}
};
return CommandManager;
})();
and the usage:
CommandManager.execute({
execute: function(){
// do something
},
unexecute: function(){
// undo something
}
});
//call unexecute of prev. command
CommandManager.undo();
//call execute of prev. command
CommandManager.redo();
My question would be, is there any advantages in defining CommandManager function this way, instead of directly defining properties on object literal and assigning it back to var CommandManager
The only use for that would be that you have a function that does absolutely nothing:
CommandManager(); // does nothing, returns undefined
Other than that, you can just as well write the code as an object literal and use this to avoid it being dependant on its own name:
var CommandManager = {
executed: [],
unexecuted: [],
execute: function execute(cmd) {
cmd.execute();
this.executed.push(cmd);
},
undo: function undo() {
var cmd1 = this.executed.pop();
if (cmd1 !== undefined){
if (cmd1.unexecute !== undefined){
cmd1.unexecute();
}
this.unexecuted.push(cmd1);
}
},
redo: function redo() {
var cmd2 = this.unexecuted.pop();
if (cmd2 === undefined){
cmd2 = this.executed.pop();
this.executed.push(cmd2);
this.executed.push(cmd2);
}
if (cmd2 !== undefined){
cmd2.execute();
this.executed.push(cmd2);
}
}
}
I have this in my AngularJS application:
$scope.user = {
numberValue: null
}
and I have this function that returns a value:
$scope.formalTrainingCosts = function () {
return $scope.user.avePaynonTrainedEmployees * $scope.user.newEmployeeFormalTrainingHours;
}
Can I save that value to the variable numberValue like so $scope.user.numberValue = $scope.formalTrainingCosts? If that does not work, what else can I do to get the function saved to the variable?
the answer by Blackhole will work but you can also put the function straight into the object. The downside to that would be that the function would be called every time you referenced that variable whereas Blackhole's answer would call the function once and save the answer.
$scope.user = {
numberValue: function() {
return $scope.user.avePaynonTrainedEmployees * $scope.user.newEmployeeFormalTrainingHours;
}
}
or
$scope.user = {
numberValue: $scope.formalTrainingCosts()
}
'use strict';
var app = angular.module('app');
app.factory('currTripService', function() {
var currtrip ='';
return{
setCurrTrip: function(trip){
currtrip = trip ;
},
getCurrTrip: function(){
return currtrip ;
},
}
});
app.controller('TripCreateController', function($scope, $location, Trip,currTripService) {
//The save method which is called when the user wants to submit their data
$scope.save = function() {
//Create the forum object to send to the back-end
var trip = new Trip($scope.trip);
console.log(trip);
currTripService.setCurrTrip(trip);
console.log(currTripService.getCurrTrip());
//Save the forum object
trip.$save(function() {
//Redirect us back to the main page
$location.path('/trip/day/1');
}, function(response) {
//Post response objects to the view
$scope.errors = response.data.errors;
});
}
});
app.controller('TripDayCreateController',function($scope,$routeParams,currTripService){
$scope.items=[];
$scope.trip = currTripService.getCurrTrip();
console.log($scope.trip.city);
// $scope.products = productService.getProducts();
$scope.addItem = function(item) {
$scope.items.push(item);
$scope.item = {};
}
});
When i click on /trip/new , its does the save in TripCreateController and set the trip object inside currTripService.
Then when redirected to TripDayCreateContoller the console.log(currTripService.getTrip()) , returns 'undefined'
Is it because Trip is an object ? How can i fix this ?
try this:
app.factory('currTripService', function() {
var currtrip = '';
var self = this;
return{
setCurrTrip: function(trip){
self.currtrip = trip ;
},
getCurrTrip: function(){
return self.currtrip ;
},
}
});
When you declare a function, this scope changes so currtrip was only existing in your getter/setter functions, but not outside.
The best way to do this is to use a class. Below is a an example of a class from CoffeeScript.
class currTripService
# storage object
#data = null
# get data
get: =>
return #data
# set data
put: (data) =>
#data = data
app.factory('currTripService', currTripService)
However if you want to do this without a class method then you can instead use something that would imitate a class:
var currTripService = function () {
// storage variable
var currTrip = null
// reference to this element
var _this = this
return{
// set this trip value
setCurrTrip: function(trip){
_this.currtrip = trip;
},
// get this trip value
getCurrTrip: function(){
return _this.currtrip;
},
}
}
app.factory('currTripService', currTripService);
Just a note: I put the function outside the factory to imitate how you'd typically call a class, but you can obviously just put all of the code in the function declaration.
app.factory('currTripService', function () {
// logic
});