I am writing a jasmine reporter, and I want to be able to pass parameters from individual specs to the reporter. For example:
the spec:
// I prefer this way
it("my spec 1", function() { ... }, { myParam: true });
// But this way would also be fine if it can work
it("my spec 2", function() {
this.myParam = true;
...
});
the reporter:
this.specDone = function(specResult) {
var myParam = // some way to access myParam
...
}
I haven't found any documentation for something like this, nor any example of something similar in other reporters.
I also tried debugging the flow of jasmine to see what objects get passed to each method but so far I had no luck in finding a simple solution.
How can this be done?
I found one possible solution - in boot.js
var jasmineInterface = {
it: function(desc, func, properties) {
var spec = env.it(desc, func);
spec.result.myParam = (properties || {}).myParam;
return spec;
},
...
and then in the reporter:
this.specDone = function(specResult) {
var myParam = specResult.myParam;
...
}
of course you can (and should) make this more generic to fit other cases.
Related
I'm writing some test for for an angularjs factory and some of the expectations are not working and I really don't know why.
This is my factory (part of it).
'use strict';
angular.module('myAppMod')
.factory('Person', function(BaseModel) {
return BaseModel.extend({
get fullname() {
var name = [];
if (this.first_name) {
name.push(this.first_name);
}
if (this.person_extra && this.person_extra.middle_name) {
name.push(this.person_extra.middle_name);
}
if (this.last_name) {
name.push(this.last_name);
}
return name.join(' ');
}
});
});
and Jasmine tests:
var p;
beforeEach(function() {
p = new Person({
first_name: 'first_name',
person_extra: {
middle_name: 'middle_name',
media_item_id: null
},
last_name: 'last_name',
security_level: 'security_level'
}, true);
});
it("has a fullname", function() {
expect(p.fullname).toEqual('first_name middle_name last_name');
});
p.fullnameis returning ""(empty string) and in the factory, console.log(this.first_name), is undefined.
Any help is really appreciated.
Thank you in advance
EDIT: After further investigation, I have changed my answer.
It is not working because you are using the getter shorthand (get fnName() { }) through the extend method. The getter's this is the anonymous object itself and does not inherit the methods and properties of the Backbone model, whereas the this in function properties do. I have made a codepen that illustrate your problem.
That is, if this is your Model
var Model = BaseModel.extend({
get isBackboneModelThroughGetter() {
return !!this.get;
},
isBackboneModel: function() {
return !!this.get;
},
});
Then an instance of Model will make this test pass:
it('should make you wonder', function() {
var model = new Model();
expect(model.isBackboneModel()).toBe(true);
expect(model.isBackboneModelThroughGetter).not.toBe(true);
});
Thus, to make your Person factory work, you will need:
To replace every property access by the proper Backbone call: this.get('propertyName') instead of this.propertyName
Replace all getters by function properties: full_name : function() { /*...*/ } instead of get full_name() { /* ... */ }
Replace calls to model.full_name by model.full_name();
I assume that you're using the built-in angular.extend. angular.extend does not copy getters and setters. There's been an open issue on GitHub on this specific subject since the 12th of August 2014.
As for why it still isn't implemented:
Angular exposes some of the helper functions that it uses internally. This is the case for extend, copy and many others. There are other libraries that specialize in these functions, keep their
focus is there and can do a better job.
It is not in the best interest of most users to make these helper functions big nor slow, as these are used internally and any change in that direction can have a direct impact in download size and performance. At the same time, apps that need the most accurate version, should be better served with other libraries.
There are many ways to solve this issue. decaf.js provides an example implementation that should work for most cases. GitHub is probably a better environment to dive into their code, but it comes down to this:
function extend (me) {
var args = Array.prototype.slice.call(arguments, 1);
decaf.each(args, function (o) {
for (var key in o) {
if (o.hasOwnProperty(key)) {
var desc = Object.getOwnPropertyDescriptor(o, key);
var g = desc.get;
var s = desc.set;
if (g || s) {
Object.defineProperty(me, key, { get: g, set: s, enumerable: true });
} else {
me[key] = o[key];
}
}
}
});
return me;
}
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()
}
I have some tightly coupled legacy code that I want to cover with tests. Sometimes it's important to ensure that one mocked out method is called before another. A simplified example:
function PageManager(page) {
this.page = page;
}
PageManager.prototype.openSettings = function(){
this.page.open();
this.page.setTitle("Settings");
};
In the test I can check that both open() and setTitle() are called:
describe("PageManager.openSettings()", function() {
beforeEach(function() {
this.page = jasmine.createSpyObj("MockPage", ["open", "setTitle"]);
this.manager = new PageManager(this.page);
this.manager.openSettings();
});
it("opens page", function() {
expect(this.page.open).toHaveBeenCalledWith();
});
it("sets page title to 'Settings'", function() {
expect(this.page.setTitle).toHaveBeenCalledWith("Settings");
});
});
But setTitle() will only work after first calling open(). I'd like to check that first page.open() is called, followed by setTitle(). I'd like to write something like this:
it("opens page before setting title", function() {
expect(this.page.open).toHaveBeenCalledBefore(this.page.setTitle);
});
But Jasmine doesn't seem to have such functionality built in.
I can hack up something like this:
beforeEach(function() {
this.page = jasmine.createSpyObj("MockPage", ["open", "setTitle"]);
this.manager = new PageManager(this.page);
// track the order of methods called
this.calls = [];
this.page.open.and.callFake(function() {
this.calls.push("open");
}.bind(this));
this.page.setTitle.and.callFake(function() {
this.calls.push("setTitle");
}.bind(this));
this.manager.openSettings();
});
it("opens page before setting title", function() {
expect(this.calls).toEqual(["open", "setTitle"]);
});
This works, but I'm wondering whether there is some simpler way to achieve this. Or some nice way to generalize this so I wouldn't need to duplicate this code in other tests.
PS. Of course the right way is to refactor the code to eliminate this kind of temporal coupling. It might not always be possible though, e.g. when interfacing with third party libraries. Anyway... I'd like to first cover the existing code with tests, modifying it as little as possible, before delving into further refactorings.
I'd like to write something like this:
it("opens page before setting title", function() {
expect(this.page.open).toHaveBeenCalledBefore(this.page.setTitle);
});
But Jasmine doesn't seem to have such functionality built in.
Looks like the Jasmine folks saw this post, because this functionality exists. I'm not sure how long it's been around -- all of their API docs back to 2.6 mention it, though none of their archived older style docs mention it.
toHaveBeenCalledBefore(expected)
expect the actual value (a Spy) to have been called before another Spy.
Parameters:
Name Type Description
expected Spy Spy that should have been called after the actual Spy.
A failure for your example looks like Expected spy open to have been called before spy setTitle.
Try this:
it("setTitle is invoked after open", function() {
var orderCop = jasmine.createSpy('orderCop');
this.page.open = jasmine.createSpy('openSpy').and.callFake(function() {
orderCop('fisrtInvoke');
});
this.page.setTitle = jasmine.createSpy('setTitleSpy').and.callFake(function() {
orderCop('secondInvoke');
});
this.manager.openSettings();
expect(orderCop.calls.count()).toBe(2);
expect(orderCop.calls.first().args[0]).toBe('firstInvoke');
expect(orderCop.calls.mostRecent().args[0]).toBe('secondInvoke');
}
EDIT: I just realized my original answer is effectively the same as the hack you mentioned in the question but with more overhead in setting up a spy. It's probably simpler doing it with your "hack" way:
it("setTitle is invoked after open", function() {
var orderCop = []
this.page.open = jasmine.createSpy('openSpy').and.callFake(function() {
orderCop.push('fisrtInvoke');
});
this.page.setTitle = jasmine.createSpy('setTitleSpy').and.callFake(function() {
orderCop.push('secondInvoke');
});
this.manager.openSettings();
expect(orderCop.length).toBe(2);
expect(orderCop[0]).toBe('firstInvoke');
expect(orderCop[1]).toBe('secondInvoke');
}
Create a fake function for the second call that expects the first call to have been made
it("opens page before setting title", function() {
// When page.setTitle is called, ensure that page.open has already been called
this.page.setTitle.and.callFake(function() {
expect(this.page.open).toHaveBeenCalled();
})
this.manager.openSettings();
});
Inspect the specific calls by using the .calls.first() and .calls.mostRecent() methods on the spy.
Basically did the same thing. I felt confident doing this because I mocked out the function behaviors with fully synchronous implementations.
it 'should invoke an options pre-mixing hook before a mixin pre-mixing hook', ->
call_sequence = []
mix_opts = {premixing_hook: -> call_sequence.push 1}
#mixin.premixing_hook = -> call_sequence.push 2
spyOn(mix_opts, 'premixing_hook').and.callThrough()
spyOn(#mixin, 'premixing_hook').and.callThrough()
class Example
Example.mixinto_proto #mixin, mix_opts, ['arg1', 'arg2']
expect(mix_opts.premixing_hook).toHaveBeenCalledWith(['arg1', 'arg2'])
expect(#mixin.premixing_hook).toHaveBeenCalledWith(['arg1', 'arg2'])
expect(call_sequence).toEqual [1, 2]
Lately I've developed a replacement for Jasmine spies, called strict-spies, which solves this problem among many others:
describe("PageManager.openSettings()", function() {
beforeEach(function() {
this.spies = new StrictSpies();
this.page = this.spies.createObj("MockPage", ["open", "setTitle"]);
this.manager = new PageManager(this.page);
this.manager.openSettings();
});
it("opens page and sets title to 'Settings'", function() {
expect(this.spies).toHaveCalls([
["open"],
["setTitle", "Settings"],
]);
});
});
I'm having a very hard time understanding how to setup an object that allows me to test my jQuery calls. I don't need to mock any Async calls or anything, just basic use. So let me set out my function that I want to test (truncated for simplicity):
listGamesCallback : function(data) {
var gameList = $("#gameList select");
gameList.empty();
$.each(data, function() {
var newOption = $('<option>', {value : this.gameId });
newOption.text(string);
newOption.data("isJoinable", isJoinable);
// Add it to the list
gameList.append(newOption);
});
}
I need to mock the jQuery here to unit test this method, but I'm unable to figure out how to do this in javascript. Even without jsMockito, I don't know how to create an object with the properties that jQuery has in this situation. Any help with this would be appreciated.
I am using jsTestDriver, jsHamcrest, jsMockito and jQuery. However a generalized approach to create a $ object that has these properties would be awesome as well. Thank you!
For those that asked, here is what I came up with that seemed to kinda work..but I don't understand why.
var saved$ = $;
var mockContruct = mockFunction();
var mockedGamelist = mock(jQuery);
var mockedOption = mock(jQuery);
mocked$ = (function() {
var test = function(name) {
var args = jQuery.makeArray(arguments);
return mockContruct.call(test, args);
};
$.extend(test, $);
// This is what confuses me. This worked, but it's wierd
// It allows me to use the regular jQuery functions like
// $.each, while returning mocked objects when selectors are used.
test.prototype.constructor = test;
return test;
})();
$ = mocked$;
when(mockContruct).call(anything(), hasItem(containsString("#gameList")))
.thenReturn(mockedGamelist);
when(mockContruct).call(anything(), hasItems(containsString("<option>"), both(object()).and(hasMember("value"))))
.thenReturn(mockedOption);
headerFunctions.listGamesCallback([ {
gameId : 1,
isWhitesTurn : false,
isGameOver : false,
whiteUserName : "foobar",
blackUserName : "barfoo"
} ]);
JsMockito.verify(mockedGamelist).empty();
JsMockito.verify(mockedGamelist).append(mockedOption);
$ = saved$;
Ok, here what I came up with that does the job with minimal setup. The .extend is completely necessary here so that the jQuery object is setup correctly. This allows you to mock the constructor to return mocked jQuery objects that you can use to run your tests on. As a spy, jQuery will work as expected in all situations except when you want it to do something else. Here it is:
TestCase("HeaderTest", {
testListGamesCallback : function () {
var saved$ = $;
$ = $.prototype.construct = jQuery.extend(spy(jQuery), jQuery);
var mockGameList = mock(jQuery);
when($)(containsString("#gameList")).thenReturn(mockGameList);
headerFunctions.listGamesCallback([ {
gameId : 1,
isWhitesTurn : false,
isGameOver : false,
whiteUserName : "foobar",
blackUserName : "barfoo"
} ]);
verify(mockGameList).empty();
verify(mockGameList).append(object());
$ = saved$;
}
});
The caveat to this solution is that mocking anything other than the constructor is a bit tricky. You will have to set each individual function that you want to mock, then program the behavior. So:
$.each = mockFunction();
when($.each)(...matchers...).thenReturn(...);
But it still allows for testing what you need to.
As an extension to alpian's answer, you can create DOM elements without having to add them to the page. Make your JS functions take the relevant elements as parameters:
listGamesCallback : function(data, gameListSelectElem) {
var gameList = $(gameListSelectElem);
...
and test them like so:
var fakeSelect = $('<select>'),
data = ...;
listGamesCallback(data, fakeSelect[0]);
equal(fakeSelect.find('option').length, 1, 'must have exactly 1 option');
...
The last line of code above is for qUnit. Take whatever you need, the point is to say you can pass a DOM element that was never added to the page and afterwards investigate that DOM element using jQuery to find whether it was manipulated right.
No sure if i understand what you mean but if you want to create 'data' for you example , this is the method i know:
var data = [ { id : 1 , name : 'foo' } , { id : 2, name : 'bar' ]
but - if you wanted to create a list of options, than you code needs a couple of fixes:
see http://jsfiddle.net/7MMap/
var data = [ { gameId : 1 , name : 'foo' ,isJoinable:true} , { gameId : 2, name : 'bar' ,isJoinable:false}]
listGamesCallback = function(data) {
var gameList = $("#gameList select")
.empty();
$.each(data, function(i,d) {
var newOption = $('<option>', {value : d.gameId })
.text(d.name)
.data("isJoinable", d.isJoinable);
// Add it to the list
gameList.append(newOption);
})
};
listGamesCallback(data);
Mocking jQuery is not what mocking is for. You should only ever be mocking your object's collaborators. jQuery is providing you with some utilities - it's not a collaborator and hence should not be mocked.
What you are collaborating with here is the DOM, or some intermediate object between your code and the DOM. data is a value object and can simply be created in your test as Avi suggests.
In my JS tests, i don't mock the DOM, i use the real DOM and am sure to tear down anything i created between tests and this seems to work pretty well.
I have a custom matcher in some Jasmine test specs of the form:
this.addMatchers({
checkContains: function(elem){
var found = false;
$.each( this.actual, function( actualItem ){
// Check if these objects contain the same properties.
found = found || actualItem.thing == elem;
});
return found;
}
});
Of course, actualItem.thing == elem doesn't actually compare object contents- I have to use one of the more complex solutions in Object comparison in JavaScript.
I can't help but notice, though, that Jasmine already has a nice object equality checker: expect(x).toEqual(y). Is there any way to use that within a custom matcher? Is there any general way to use matchers within custom matchers?
Yes, it is slightly hacky but entirely possible.
The first thing we need to do is make the Jasmine.Env class available. Personally I have done this in my SpecRunner.html since its already setup there anyway. On the load of my SpecRunner I have the following script that runs:
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var trivialReporter = new jasmine.TrivialReporter();
jasmineEnv.addReporter(trivialReporter);
jasmineEnv.specFilter = function(spec) {
return trivialReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
};
})();
So after the execJasmine function declaration I push the jasmineEnv into the global namespace by adding this:
this.jasmineEnv = jasmineEnv;
Now, in any of my spec files I can access the jasmineEnv variable and that is what contains the matchers core code.
Looking at toEqual specifically, toEqual calls the jasmine.Env.prototype.equals_ function. This means that in your customMatcher you can do the following:
beforeEach(function(){
this.addMatchers({
isJasmineAwesome : function(expected){
return jasmineEnv.equals_(this.actual, expected);
}
});
});
Unfortunately, using this method will only give you access to the following methods:
compareObjects_
equals_
contains_
The rest of the matchers reside the jasmine.Matchers class but I have not been able to make that public yet. I hope this helps you out in someway or another