Javascript testing, node + sinon, testing 'new' calls - javascript

I am trying to test a function that looks like so
ContentModel.prototype.fileHandlers = function() {
if (_.isUndefined(this.__cache__.fileHandler)) {
this.__cache__.fileHandlers = new FileHandlers(this.__data__.fileHandlers);
}
return this.__cache__.fileHandlers;
};
to simply check that it caches how I have it set up like so
it("should return cached the second time.", function() {
contentModel = new ContentModel({
fileHandlers: {}
});
var firstTime = contentModel.fileHandlers();
var secondTime = contentModel.fileHandlers();
expect(firstTime).to.equal(secondTime);
});
And getting the errors of :
AssertionError: expected { Object (__data__, __cache__) } to equal { Object (__data__, __cache__) }
+ expected - actual
I just want to basically check the second call is the same - so it's cached when I use the new ContentModel. Can't seem to figure out how to wrestle down this problem. It's sort of an odd problem, but I am going for as much coverage as possible. Thanks!
Just to clarify a little further - I can change .to.equal to to.deep.equal and the test will pass, however I want to check if the object is the same object being returned, not the content.

Related

Karma - test function with no return and which sets no scope variable

I've been reading up on karma mainly and jasmine a little and have begun to implement testing on my app.
I have the following function :
$scope.popup1 = function (isinData) {
var popup1 = window.open("views/Box_Ladder.html", "_blank",
"height = 400, width = 700");
shareDataService.setIsinClickValue(isinData);
}
How on earth do I test this using karma? The expected result is a popup window opening and the relevant data being passed to my service. How do I expect this?
You spy on window.open and expect it to be called with the right arguments.
Even if the function doesn't return something, it should at the minimum cause some side-effect. You need to test the side-effects.
To do this, create and inject a mock object + object method. An example would be as follows:
var window = {
open: function(url, target, specs) {
var spec, specKey;
this.href = url;
this.target = target;
// Parse through the spec string to grab the parameters you passed through
var specArray = specs.split(',');
for (specKey in specArray) {
spec = specArray[specKey].split('=');
this[String.trim(spec[0])] = String.trim(spec[1]);
}
}
};
Now you can expect(window.href).toEqual(url), expect(window.target).toEqual(target), expect(window.height).toEqual(400), etc.
Additionally, you need to see if sharedDataService.setIsinClickValue was invoked. If you cannot access this service within your test, you're going to have to create another mock object + method.

Custom browser actions in Protractor

The problem:
In one of our tests we have a "long click"/"click and hold" functionality that we solve by using:
browser.actions().mouseDown(element).perform();
browser.sleep(5000);
browser.actions().mouseUp(element).perform();
Which we would like to ideally solve in one line by having sleep() a part of the action chain:
browser.actions().mouseDown(element).sleep(5000).mouseUp(element).perform();
Clearly, this would not work since there is no "sleep" action.
Another practical example could be the "human-like typing". For instance:
browser.actions().mouseMove(element).click()
.sendKeys("t").sleep(50) // we should randomize the delays, strictly speaking
.sendKeys("e").sleep(10)
.sendKeys("s").sleep(20)
.sendKeys("t")
.perform();
Note that these are just examples, the question is meant to be generic.
The Question:
Is it possible to extend browser.actions() action sequences and introduce custom actions?
Yes, you can extend the actions framework. But, strictly speaking, getting something like:
browser.actions().mouseDown(element).sleep(5000).mouseUp(element).perform();
means messing with Selenium's guts. So, YMMV.
Note that the Protractor documentation refers to webdriver.WebDriver.prototype.actions when explaining actions, which I take to mean that it does not modify or add to what Selenium provides.
The class of object returned by webdriver.WebDriver.prototype.actions is webdriver.ActionSequence. The method that actually causes the sequence to do anything is webdriver.ActionSequence.prototype.perform. In the default implementation, this function takes the commands that were recorded when you called .sendKeys() or .mouseDown() and has the driver to which the ActionSequence is associated schedule them in order. So adding a .sleep method CANNOT be done this way:
webdriver.ActionSequence.prototype.sleep = function (delay) {
var driver = this.driver_;
driver.sleep(delay);
return this;
};
Otherwise, the sleep would happen out of order. What you have to do is record the effect you want so that it is executed later.
Now, the other thing to consider is that the default .perform() only expects to execute webdriver.Command, which are commands to be sent to the browser. Sleeping is not one such command. So .perform() has to be modified to handle what we are going to record with .sleep(). In the code below I've opted to have .sleep() record a function and modified .perform() to handle functions in addition to webdriver.Command.
Here is what the whole thing looks like, once put together. I've first given an example using stock Selenium and then added the patches and an example using the modified code.
var webdriver = require('selenium-webdriver');
var By = webdriver.By;
var until = webdriver.until;
var chrome = require('selenium-webdriver/chrome');
// Do it using what Selenium inherently provides.
var browser = new chrome.Driver();
browser.get("http://www.google.com");
browser.findElement(By.name("q")).click();
browser.actions().sendKeys("foo").perform();
browser.sleep(2000);
browser.actions().sendKeys("bar").perform();
browser.sleep(2000);
// Do it with an extended ActionSequence.
webdriver.ActionSequence.prototype.sleep = function (delay) {
var driver = this.driver_;
// This just records the action in an array. this.schedule_ is part of
// the "stock" code.
this.schedule_("sleep", function () { driver.sleep(delay); });
return this;
};
webdriver.ActionSequence.prototype.perform = function () {
var actions = this.actions_.slice();
var driver = this.driver_;
return driver.controlFlow().execute(function() {
actions.forEach(function(action) {
var command = action.command;
// This is a new test to distinguish functions, which
// require handling one way and the usual commands which
// require a different handling.
if (typeof command === "function")
// This puts the command in its proper place within
// the control flow that was created above
// (driver.controlFlow()).
driver.flow_.execute(command);
else
driver.schedule(command, action.description);
});
}, 'ActionSequence.perform');
};
browser.get("http://www.google.com");
browser.findElement(By.name("q")).click();
browser.actions().sendKeys("foo")
.sleep(2000)
.sendKeys("bar")
.sleep(2000)
.perform();
browser.quit();
In my implementation of .perform() I've replaced the goog... functions that Selenium's code uses with stock JavaScript.
Here is what I did (based on the perfect #Louis's answer).
Put the following into onPrepare() in the protractor config:
// extending action sequences
protractor.ActionSequence.prototype.sleep = function (delay) {
var driver = this.driver_;
this.schedule_("sleep", function () { driver.sleep(delay); });
return this;
};
protractor.ActionSequence.prototype.perform = function () {
var actions = this.actions_.slice();
var driver = this.driver_;
return driver.controlFlow().execute(function() {
actions.forEach(function(action) {
var command = action.command;
if (typeof command === "function")
driver.flow_.execute(command);
else
driver.schedule(command, action.description);
});
}, 'ActionSequence.perform');
};
protractor.ActionSequence.prototype.clickAndHold = function (elm) {
return this.mouseDown(elm).sleep(3000).mouseUp(elm);
};
Now you'll have sleep() and clickAndHold() browser actions available. Example usage:
browser.actions().clickAndHold(element).perform();
I think it is possible to extend the browser.actions() function but that is currently above my skill level so I'll lay out the route that I would take to solve this issue. I would recommend setting up a "HelperFunctions.js" Page Object that will contain all of these Global Helper Functions. In that file you can list your browser functions and reference it in multiple tests with all of the code in one location.
This is the code for the "HelperFunctions.js" file that I would recommend setting up:
var HelperFunctions = function() {
this.longClick = function(targetElement) {
browser.actions().mouseDown(targetElement).perform();
browser.sleep(5000);
browser.actions().mouseUp(targetElement).perform();
};
};
module.exports = new HelperFunctions();
Then in your Test you can reference the Helper file like this:
var HelperFunctions = require('../File_Path_To/HelperFunctions.js');
describe('Example Test', function() {
beforeEach(function() {
this.helperFunctions = HelperFunctions;
browser.get('http://www.example.com/');
});
it('Should test something.', function() {
var Element = element(by.className('targetedClassName'));
this.helperFunctions.longClick(Element);
});
});
In my Test Suite I have a few Helper files setup and they are referenced through out all of my Tests.
I have very little knowledge of selenium or protractor, but I'll give it a shot.
This assumes that
browser.actions().mouseDown(element).mouseUp(element).perform();
is valid syntax for your issue, if so then this would likely do the trick
browser.action().sleep = function(){
browser.sleep.apply(this, arguments);
return browser.action()
}

Testing nested object with jasmine

Here is my Test
describe("setTimer", function () {
it("set status timer values from parameters and sets timer.visible to true", function(){
var boxNumber = 1,
time = 15;
myObject.setTimer(boxNumber, time);
expect(anotherObject.status.timer.boxNum).toBe(boxNumber);
expect(anotherObject.status.timer.seconds).toBe(time);
})
});
Here is the code
setTimer: function (boxNum, seconds) {
anotherObject.status.timer.boxNum = boxNum;
anotherObject.status.timer.seconds = seconds;
anotherObject.status.timer.visible = true;
},
Here is the error I am getting
TypeError: Cannot read property 'timer' of undefined
I tried setting the object using anotherObject = {} I tried setting anotherObject.status = {} and lastly tried setting anotherObject.status.timer = {}, however I still get the error. Any ideas, how can I mock the object?
Without knowing how/where 'anotherObject' is constructed I would think that you would need to initialize the 'anotherObject' before you execute the setTimer function in your test.
Do you have an init() or setup() function that exists on 'anotherObject' that would initialize the 'timer' object for you?
Although the method looks like it is just trying to make sure that the method is setting all the corresponding properties.
You could do the following before calling setTimer in your test
describe("setTimer", function () {
it("set status timer values from parameters and sets timer.visible to true", function(){
var boxNumber = 1,
time = 15;
//Initialize the anotherObject
anotherObject.status = { timer : {} }
myObject.setTimer(boxNumber, time);
expect(anotherObject.status.timer.boxNum).toBe(boxNumber);
expect(anotherObject.status.timer.seconds).toBe(time);
})
});
This of course comes with the caveat that you have now defined an 'anotherObject' inside your test using the global scope (since excluding the var on any variable definition in javascript makes it global scope). This could effect other test cases that expect the timer object to be setup a certain way but your test case has now set the timer values to 1 and 15 respectively (could be alot of other values all depending on what the test case is doing).
So to help with this, resetting the 'anotherObject' at the beginning or end of your tests would help with pollution
afterEach(function(){
anotherObject.status = { timer : {} }
})
or
beforeEach(function(){
anotherObject.status = { timer : {} }
})
Of course if you have an init(), create() or setup() function on the 'anotherObject' that could be used it would of course give you more realistic results since the object would be much closer to what it would look like in production.
You are not working on the same "anotherObject" object in both source and test codes.
Each code has it's own object and setting values to one will not set in the other.

How to implement chained stub in javascript with Jasmine and AngularJs?

I'm not sure if I'm writing the title correct, but here's what I want to do.
I have this code
var callback = function(result) {
if(result.count < 5) {
msg_id = result.msg_id;
MovieService.getMovies(msg_id, result.count).get(callback, error);
}
if(result.movies.length !== 0) {
setDataToDisplay(result);
}
if(result.count === 5) {
$scope.loading = false;
}
}
MovieService.getMovies(msg_id, 0).get(callback, error);
Basically, when user comes in the first time MovieService will be called and it gets called until the count equals to 5 times. It's like a recursive loop. Now if I want to test this code, I don't know how to do chained stub in Jasmine. I could do something similar in Mockito.
Here's my test so far.
it("should give me the lot of movies", function() {
var movie1 = new MovieBuilder().withTweetId('8').build();
var movie2 = new MovieBuilder().withId('3812').withTweetId('8').build();
var movie3 = new MovieBuilder().withId('3813').withTweetId('8').build();
var movie4 = new MovieBuilder().withId('3814').withTweetId('8').build();
movieService = {
getMovies : function() {
return {
get : function(callback, error) {
callback(
{
'msg_id' : '8',
'count' : '5',
'movies' : [movie1, movie2, movie3, movie4]
});
}
}
}
}
ctrl = controller('MovieTwitterCtrl', {$scope : scope, MovieService : movieService});
expect(scope.movie_groups[0].length).toBe(4);
expect(scope.msg_id).toBe('8');
});
But if I want to test the second, third, fourth and fifth call. How do I do that? Does Jasmine offer something like Mockito? Or how do I do that in pure javascript?
Thanks a lot.
You might want to take a look at Sinon, which is a library that provides methods for spys, stubs and mocks and is compatible with Jasmine.
In order to automatically invoke your callbacks, you would use stub.yields() or stub.yieldsTo(). You've also got spy.getCall(n) that will let you verify the way your method was called during the nth time. Sinon is written in a way that stubs are also spies... so if you create a stub for your movieService, you'll have access to both yields() and getCall(n).

DevTools Console - Turn it off

I'm currently building a library in Javascript and really like Google's DevTools for debugging it. Unfortunately I don't want my library to log when I release.
This is how my logger is currently setup.
var debug = false;
var increaseSomething = function()
{
// Random Code...
if (debug) { console.log("Increased!"); }
}
Unfortunately this is quite annoying, I shouldn't have to check if debug is on before logging to the console every call.
I could try to encapsulate the console in my own logging object but I feel that wouldn't be such a great idea. Any thoughts?
You could do this?
if (!debug) {
console.log = function() {/* No-op */}
}
As you mentioned, you might not want to kill all logging for everyone. This is how I usually go about it. Define these in some utility file, as global functions. I usually add additional functions for LOG, WARN, ERROR and TRACE, and log these based on a verbosity level.
// Define some verbosity levels, and the current setting.
_verbosityLevels = ["TRACE", "LOG", "WARN", "ERROR"];
_verbosityCurrent = _verbosityLevels.indexOf("LOG");
// Helper function.
var checkVerbosity = function(level) {
return _verbosityLevels.indexOf(level) <= _verbosityCurrent;
}
// Internal log function.
var _log = function(msg, level) {
if(!debug && checkVerbosity(level)) console.log(msg);
}
// Default no-op logging functions.
LOG = function() {/* no-op */}
WARN = function() {/* no-op */}
// Override if console exists.
if (console && console.log) {
LOG = function(msg) {
_log(msg, "LOG");
}
WARN = function(msg) {
_log(msg, "WARN");
}
}
This also allows you to add important information to your log, such as time, and caller locations.
console.log(time + ", " + arguments.callee.caller.name + "(), " + msg);
This may output something like this:
"10:24:10.123, Foo(), An error occurred in the function Foo()"
I thought about encapsulating the console logger again and instead of coming up with an entire object to encapsulate the console I created a function that takes in a console method. Then it checks if debugging is on and calls the function.
var debug = true;
var log = function (logFunction) {
if (debug) {
logFunction.apply(console, Array.prototype.slice.call(arguments, 1));
}
};
var check = function (canvas) {
log(console.groupCollapsed, "Initializing WebGL for Canvas: %O", canvas);
log(console.log, "cool");
log(console.groupEnd);
};
check(document.getElementById('thing'));
I do like #Aesthete's ideas but I'm not yet wanting to make the encapsulated console.
Here is the jsfiddle as example: http://jsfiddle.net/WRe29/
Here I add a debugCall to the Objects prototype. Same as the log function just a different name so theirs no 'overlap' Now any object can call debugCall and check its debug flag.
Object.prototype.debugCall = function(logFunction)
{
if (this.debug) { logFunction.apply(console, Array.prototype.slice.call(arguments, 1)); }
};
var Thing = { debug : true /*, other properties*/ };
Thing.debugCall(console.log, "hello world");
EDIT:
My initial thoughts were to use an object as the 'configuration' to indicate whether the object should be logging. I've used this a while and liked the configuration concept but didn't think everyone would be so keen to use configuration objects in their code alongside a function being passed to a extended function on object. Thus I took the concept and instead looked at function decoration.
Function.prototype.if = function (exp) {
var exFn = this;
return function () {
if (exp) exFn.apply(this, arguments);
};
};
var debug = false;
console.log = console.log.if(debug);
console.group = console.group.if(debug);
// Console functions...
myFunction = myFunction.if(debug);
It's very simple almost unnecessary to even have a decoration function that checks an expression but I am not willing to put if statements everywhere in my code. Hope this helps someone out maybe even spark their interest with function decoration.
Note: This way will also kill logging for everyone unless you setup the if extension correctly ;) *cough some type of object/library configuration indicating debug
https://github.com/pimterry/loglevel
Log level Library::Try whether this suits ur need.

Categories

Resources