This question is not about including a service to test or to provide a mock that replaces a service.
Situation:
The Factory I'd like to test is about parsing a set of properties and provide this information via getter functions. The following pseudocode describes what is happening right now (supprisingly it works, although it is quite hacky to create dynamically tests depending on the data length.)
...
describe('fancyTest', function() {
// 1
beforeEach(function() {
// 7
module('app');
inject(function($injector) {
// 8
Factory = $injector.get('app.ToTestFactory');
UtilService = $injector.get('app.Util'); // bad, as to late...
});
});
// 2
describe('dataTest', function() {
// 3
// Goal: data = UtilService.getData('dataset1');
data = [{id:'test1'}, {id:'test2'}];
for (i = 0, l = data.length; i < l; i += 1) {
test(data[i]);
}
function test(properties) {
// 4
describe('datatest #' + i, function() {
var elem;
// 5
beforeEach(function() {
// 9
elem = new Factory(properties)
});
// 6
it('should provide the correct id', function() {
// 10
expect(elem.id()).toBe(properties.id);
});
...
});
}
}
...
}
The UtilService.getData() is a simple method that reads the data out of some constants that are only injected when executing tests. It's maybe important, that I dont want to load them asynchronous.
Problem:
The Jasmine framework has a quite uninituitive workflow and first runs and initializes all describe blocks, before it runs through the beforeEach. The order is written in the comments.
Do I have no chance to inject the UtilService before the data-loop runs through?
Thanks for helping me out!
If your UtilService is a factory, you can inject the service into every test with the before each
beforeEach(function() {
module('app');
var UtilService;
inject(function(_UtilService_) {
UtilService = _UtilService_;
});
});
The way jasmine sets up tests, as long as you load all of your dependencies properly in your Karma Config, you shouldn't ever have to use $injector to pull in a service.
And because the underscores are probably a little confusing, angular will provide an underscored service so you can create a new instance every time.
Related
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"],
]);
});
});
It seems that Angular does not provide a built-in solution to define class instances with properties and methods and that it's up the developer to build this.
What is the best practice to do this in your opinion?
How to you link this with the backend?
Some of the tips I have gathered use factory services and named functions.
Sources :
Tuto 1
Tuto 2
Thanks for your insights
I think that the closest structure to an Object it's probably a factory, for several reasons:
Basic Syntax:
.factory('myFactory', function (anInjectable) {
// This can be seen as a private function, since cannot
// be accessed from outside of the factory
var privateFunction = function (data) {
// do something
return data
}
// Here you can have some logic that will be run when
// you instantiate the factory
var somethingUseful = anInjectable.get()
var newThing = privateFunction(somethingUseful)
// Here starts your public APIs (public methods)
return {
iAmTrue: function () {
return true
},
iAmFalse: function () {
return false
},
iAmConfused: function () {
return null
}
}
})
And then you can use it like a standard Object:
var obj = new myFactory()
// This will of course print 'true'
console.log( obj.iAmTrue() )
Hope this helps, I perfectly know that the first impact with angular modules can be pretty intense...
You would use an angular service.
All angular services are singletons and can be injected into any controller.
Ideally you would keep only binding/actions on html in your controller and the rest of the logic would be in your service.
Hope this helps.
I got idea by evaluating this library : https://github.com/FacultyCreative/ngActiveResource
However this library assumes strict rest so I it wasn't work for me. What did work for is this:
I created base Model
var app = angular.module('app', []);
app .factory('Model', function(){
var _cache = {}; // holding existing instances
function Model() {
var _primaryKey = 'ID',
_this = this;
_this.new = function(data) {
// Here is factory for creating instances or
// extending existing ones with data provided
}
}
return Model;
});
Than I took simple function extensions "inherits"
Function.prototype.inherits = function (base) {
var _constructor;
_constructor = this;
return _constructor = base.apply(_constructor);
};
and now I cam creating my models like this
app.factory('Blog', [
'Model',
'$http',
function(Model, $http) {
function Blog() {
// my custom properties and computations goes here
Object.defineProperty(this, 'MyComputed' , {
get: function() { return this.Prop1 + this.Prop2 }
});
}
// Set blog to inherits model
Blog.inherits(Model);
// My crud operations
Blog.get = function(id) {
return $http.get('/some/url', {params: {id:id}}).then(function(response) {
return Blog.new(response.data);
});
}
return Blog;
}
]);
Finally, using it in controller
app.controller('MyCtrl', [
'$scope', 'Blog',
function($scope, Blog) {
Blog.get(...).then(function(blog) {
$scope.blog = blog;
});
}
])
Now, there is much more in our Model and extensions but this would be a main principle. I am not claiming this is best approach but I am working pretty big app and it really works great for me.
NOTE: Please note that I typed this code here and could be some errors but main principle is here.
As my question does not really reflect the issue I was facing, I'll just post my approach for the sake of it :
As Domokun put it, rule of thumb is to decouple front and back. But as I am only building a prototype and managing both ends, I would like to keep things in only one place and let the rest of the application use the central information as a service.
What I want to do here is to build a form through ng-repeat containing the model fields and most importantly how to display information in the form (e.g. 'Last name' instead of 'lastname')
So as I started working around with mongoose models here's what I have managed to do :
Firstly, it is possible to pass the mongoose schema of a model from node side to angular side with an app.get request with the following response :
res.send(mongoose.model('resources').schema.paths);
this spitts out an object containing all fields of the 'resources' collection. On top of that I included some additional information in the model like this :
var resourceSchema = new Schema({
_id: { type: Number },
firstname: { type: String, display:'First name' },
lastname: { type: String, display:'Last name' }
});
mongoose.model('resources', resourceSchema);
So basically I can retrieve this symmetrically on angular side and I have all I need to map the fields and display them nicely. It seems I can also describe the validation but I'm not there yet.
Any constructive feedback on this approach (whether it is valid or totally heretic) is appreciated.
I am trying to test angular service which does some manipulations to DOM via $document service with jasmine.
Let's say it simply appends some directive to the <body> element.
Such service could look like
(function(module) {
module.service('myService', [
'$document',
function($document) {
this.doTheJob = function() {
$document.find('body').append('<my-directive></my directive>');
};
}
]);
})(angular.module('my-app'));
And I want to test it like this
describe('Sample test' function() {
var myService;
var mockDoc;
beforeEach(function() {
module('my-app');
// Initialize mock somehow. Below won't work indeed, it just shows the intent
mockDoc = angular.element('<html><head></head><body></body></html>');
module(function($provide) {
$provide.value('$document', mockDoc);
});
});
beforeEach(inject(function(_myService_) {
myService = _myService_;
}));
it('should append my-directive to body element', function() {
myService.doTheJob();
// Check mock's body to contain target directive
expect(mockDoc.find('body').html()).toContain('<my-directive></my-directive>');
});
});
So the question is what would be the best way to create such mock?
Testing with real document will give us much trouble cleaning up after each test and does not look like a way to go with.
I've also tried to create a new real document instance before each test, yet ended up with different failures.
Creating an object like below and checking whatever variable works but looks very ugly
var whatever = [];
var fakeDoc = {
find: function(tag) {
if (tag == 'body') {
return function() {
var self = this;
this.append = function(content) {
whatever.add(content);
return self;
};
};
}
}
}
I feel that I'm missing something important here and doing something very wrong.
Any help is much appreciated.
You don't need to mock the $document service in such a case. It's easier just to use its actual implementation:
describe('Sample test', function() {
var myService;
var $document;
beforeEach(function() {
module('plunker');
});
beforeEach(inject(function(_myService_, _$document_) {
myService = _myService_;
$document = _$document_;
}));
it('should append my-directive to body element', function() {
myService.doTheJob();
expect($document.find('body').html()).toContain('<my-directive></my-directive>');
});
});
Plunker here.
If you really need to mock it out, then I guess you'll have to do it the way you did:
$documentMock = { ... }
But that can break other things that rely on the $document service itself (such a directive that uses createElement, for instance).
UPDATE
If you need to restore the document back to a consistent state after each test, you can do something along these lines:
afterEach(function() {
$document.find('body').html(''); // or $document.find('body').empty()
// if jQuery is available
});
Plunker here (I had to use another container otherwise Jasmine results wouldn't be rendered).
As #AlexanderNyrkov pointed out in the comments, both Jasmine and Karma have their own stuff inside the body tag, and wiping them out by emptying the document body doesn't seem like a good idea.
UPDATE 2
I've managed to partially mock the $document service so you can use the actual page document and restore everything to a valid state:
beforeEach(function() {
module('plunker');
$document = angular.element(document); // This is exactly what Angular does
$document.find('body').append('<content></content>');
var originalFind = $document.find;
$document.find = function(selector) {
if (selector === 'body') {
return originalFind.call($document, 'body').find('content');
} else {
return originalFind.call($document, selector);
}
}
module(function($provide) {
$provide.value('$document', $document);
});
});
afterEach(function() {
$document.find('body').html('');
});
Plunker here.
The idea is to replace the body tag with a new one that your SUT can freely manipulate and your test can safely clear at the end of every spec.
You can create an empty test document using DOMImplementation#createHTMLDocument():
describe('myService', function() {
var $body;
beforeEach(function() {
var doc;
// Create an empty test document based on the current document.
doc = document.implementation.createHTMLDocument();
// Save a reference to the test document's body, for asserting
// changes to it in our tests.
$body = $(doc.body);
// Load our app module and a custom, anonymous module.
module('myApp', function($provide) {
// Declare that this anonymous module provides a service
// called $document that will supersede the built-in $document
// service, injecting our empty test document instead.
$provide.value('$document', $(doc));
});
// ...
});
// ...
});
Because you're creating a new, empty document for each test, you won't interfere with the page running your tests and you won't have to explicitly clean up after your service between tests.
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