I am building the scaffolding for my new polymer project, and am considering unit tests. I think I will be using the karma/jasmine combination. There is an interesting post at http://japhr.blogspot.co.uk/2014/03/polymer-page-objects-and-jasmine-20.html which I understand enough to get me started, but the key question I will have to address and haven't found any standard way to do it is how do I mock the ajax calls.
When I was using jasmine, standalone, on a JQuery Mobile project, I was able to directly use the Jasmine SpyOn ability to mock the JQuery.ajax call. Is there something similar for Polymer?
I came across an element <polymer-mock-data> but there is no real documentation for it, so I couldn't figure out if they might help
Instead of importing core-ajax/core-ajax.html, create your own core-ajax element.
<polymer-element name="core-ajax" attributes="response">
<script>
Polymer('core-ajax', {
attached: function() {
this.response = ['a', 'b', 'c'];
}
});
</script>
</polymer-element>
Obviously, this is just an example, the actual implementation depends on the desired mocking behavior.
This is just one way to solve it, there are many others. I'm interested to hear what you find (in)convenient.
It turns out that Jasmine2.0 has an Jasmine-ajax plugin that will mock the global XMLHttpRequest. core-ajax uses this under the hood, so I can directly get at the call.
It works well, in a beforeEach function at the top the suite you call jasmine.Ajax.install and in the afterEach function you call jasmine.Ajax.uninstall, and it automatically replaces the XMLHttpRequest.
Timing is also crucial, in that you need to ensure you have mocked the Ajax call before the element under test uses it. I achieve that using a separate function to specifically load the fixture which contains the element under test, which is called after jasmine.Ajax.install has been called. I use a special setup script thus
(function(){
var PolymerTests = {};
//I am not sure if we can just do this once, or for every test. I am hoping just once
var script = document.createElement("script");
script.src = "/base/components/platform/platform.js";
document.getElementsByTagName("head")[0].appendChild(script);
var POLYMER_READY = false;
var container; //Used to hold fixture
PolymerTests.loadFixture = function(fixture,done) {
window.addEventListener('polymer-ready', function(){
POLYMER_READY = true;
done();
});
container = document.createElement("div");
container.innerHTML = window.__html__[fixture];
document.body.appendChild(container);
if (POLYMER_READY) done();
};
//After every test, we remove the fixture
afterEach(function(){
document.body.removeChild(container);
});
window.PolymerTests = PolymerTests;
})();
The only point to note here is that the fixture files have been loaded by the karma html2js pre-processor, which loads them into the window.__html__ array, from where we use the code to add to the test context
My test suite is like so
describe('<smf-auth>',function(){
beforeEach(function(done){
jasmine.Ajax.install();
PolymerTests.loadFixture('client/smf-auth/smf-auth-fixture.html',done);
});
afterEach(function(){
jasmine.Ajax.uninstall();
});
describe("The element authenticates",function(){
it("Should Make an Ajax Request to the url given in the login Attribute",function(){
var req = jasmine.Ajax.requests;
expect(req.mostRecent().url).toBe('/football/auth_json.php'); //Url declared in our fixture
});
})
});
For this answer, I took an entirely different approach. Inspiration came from Web Component Tester, which includes sinon within its capabilities. sinon includes the ability to call sinon.useFakeXMLHttpRequest to replace the standard xhr object that core-ajax uses and return responses baked on that.
As far as I can see, haven't quite got as far as running module tests using it, Web Component Tester runs sinon in the node.js context so the build of sinon supplied with it can "require" the various sinon components. In a normal browser environment this doesn't work and I was looking for a way to allow me to manually run the app I was developing without a php capable server running..
However, downloading and installing with Bower the actual releases from the sinonjs.org web site, does provide a completely built sinon that will run in the context of a web server.
So I can include the following scripts in my main index.html file
<!--build:remove -->
<script type="text/javascript" src="/bower_components/sinon-1.14.1/index.js"></script>
<script type="text/javascript" src="/fake/fake.js"></script>
<!--endbuild-->
which is automatically removed by the gulp build scrips and then fake JS has the following in it
var PAS = (function (my) {
'use strict';
my.Faker = my.Faker || {};
var getLocation = function(href) {
var a = document.createElement('a');
a.href = href;
return a;
};
sinon.FakeXMLHttpRequest.useFilters = true;
sinon.FakeXMLHttpRequest.addFilter(function(method,url){
if(method === 'POST' && getLocation(url).pathname.substring(0,7) === '/serve/') {
return false;
}
return true;
});
var server = sinon.fakeServer.create();
server.autoRespond = true;
my.Faker.addRoute = function(route,params,notfound){
server.respondWith('POST','/serve/' + route + '.php',function(request){
var postParams = JSON.parse(request.requestBody);
var foundMatch = false;
var allMatch;
/*
* First off, we will work our way through the parameter list seeing if we got a parameter
* which matches the parameters received from our post. If all components of a parameter match,
* then we found one
*/
for(var i=0; i <params.length; i++) {
//check to see parameter is in request
var p = params[i][0];
allMatch = true; //start of optimisic
for(var cp in p ) {
//see if this parameter was in the request body
if(typeof postParams[cp] === 'undefined') {
allMatch = false;
break;
}
if(p[cp] !== postParams[cp]) {
allMatch = false;
break;
}
}
if (allMatch) {
request.respond(200,{'Content-Type':'application/json'},JSON.stringify(params[i][1]));
foundMatch = true;
break;
}
}
//see if we found a match. If not, then we will have to respond with the not found option
if (!foundMatch) {
request.respond(200,{'Content-Type':'application/json'},JSON.stringify(notfound));
}
});
};
return my;
})(PAS||{});
/**********************************************************************
Thses are all the routinee we have and their responses.
**********************************************************************/
PAS.Faker.addRoute('logon',[
[{password:'password1',username:'alan'},{isLoggedOn:true,userID:1,name:'Alan',token:'',keys:['A','M']}],
[{username:'alan'},{isLoggedIn:false,userID:1,name:'Alan'}],
[{password:'password2',username:'babs'},{isLoggedOn:true,userID:2,name:'Barbara',token:'',keys:['M']}],
[{username:'babs'},{isLoggedIn:false,userID:2,name:'Barbara'}]
],{isLoggedOn:false,userID:0,name:''});
The PAS function initialises a sinon fake server and provides a way of providing tests cases with the addRoute function. For a given route, it checks the list of possible POST parameter combinations, and as soon as it finds one, issues that response.
In this case testing /serve/logon.php for various combinations of username and password. It only checks the parameters actually in the particular entry.
So if username = "alan" and password = "password1" the first response is made, but if username is "alan" and any other password is supplied - since it isn't checked, the second pattern matches and the response to that pattern is made.
If non of the patterns match, the last "notfound" parameter is the response pattern that is made.
I believe I could use this same technique in my module test fixtures if I wanted to, but I am more likely to do more specific sinon spying and checking actual parameters in that mode
For 0.8, the tests for PolylmerElements/iron-ajax show how to do this with sinon.
Since SO doesn't like link-only answers, I've copied their code below. However I'd highly recommend going to the source linked above, since 0.8 components are in a high state of flux currently.
var jsonResponseHeaders = {
'Content-Type': 'application/json'
};
var ajax;
var request;
var server;
setup(function () {
server = sinon.fakeServer.create();
server.respondWith(
'GET',
'/responds_to_get_with_json',
[
200,
jsonResponseHeaders,
'{"success":true}'
]
);
server.respondWith(
'POST',
'/responds_to_post_with_json',
[
200,
jsonResponseHeaders,
'{"post_success":true}'
]
);
ajax = fixture('TrivialGet');
});
teardown(function () {
server.restore();
});
suite('when making simple GET requests for JSON', function () {
test('has sane defaults that love you', function () {
request = ajax.generateRequest();
server.respond();
expect(request.response).to.be.ok;
expect(request.response).to.be.an('object');
expect(request.response.success).to.be.equal(true);
});
test('will be asynchronous by default', function () {
expect(ajax.toRequestOptions().async).to.be.eql(true);
});
});
I have written a cloud code function called approveDish that works within the timeout limits set by Parse.com when I call the function directly once from a client button.
However, I need to migrate some old database records to this and am wondering why when I run the background job with larger than say 3 results returned in the query I get timeouts in that cloud code function. Shouldn't this in a background job not timeout ever as we're calling things seriously?
Parse.Cloud.job("migrateDishesToChains", function(request, status){
Parse.Cloud.useMasterKey();
var Dishes = Parse.Object.extend("Dishes");
var query = new Parse.Query(Dishes);
query.notEqualTo("approved", true);
//dishes.equalTo("user", "JQd58QhOCO");
query.limit(1);
query.find().then(function(results) {
// Create a trivial resolved promise as a base case.
var promise = Parse.Promise.as();
_.each(results, function(result) {
// For each item, extend the promise with a function to delete it.
promise = promise.then(function() {
// Return a promise that will be resolved when the delete is finished.
return Parse.Cloud.run("approveDish", {dishID: result.id});
});
});
return promise;
}).then(function() {
status.success();
});
});
I've create this piece of code:
app.controller('SiteDetailCtrl',function($scope, $routeParams, $http){
//remove ":" in SiteId
var SiteId = $routeParams.SiteId.replace(':','');
$scope.Site = $http.get('path-to-ajax/Site/'+SiteId);
.success(function(data){
$scope.Site = data;
console.log($scope.Site);
})
.error(function(){
$scope.Site = 'NULL';
alert('Ajax Fail');
});
console.log($scope.Site);
});
I don't understand why $scope.Site is available in Success function but outside ($http) $scope.Site is null.
Please explain for me what happen here.
I'm new to AngularJS.
the successhandler function you define in line 6 will run way later than the console.log statement from line 14. it is nothing angular specific but how asynchronous code works. i suggest you research a bit in that direction.
I haven't tested it yet but apparently the fact that your're returning a value from $http $scope.Site = $http.get(... //its worng and as Valerij said the nature of $http is ajax meaning asynchronous result, causes this bug.
you need to use it like this:
app.controller('SiteDetailCtrl',function($scope, $routeParams, $http){
//remove ":" in SiteId
var SiteId = $routeParams.SiteId.replace(':','');
$http.get('path-to-ajax/Site/'+SiteId);
.success(function(data){
$scope.Site = data;
console.log($scope.Site);
})
.error(function(){
$scope.Site = 'NULL';
alert('Ajax Fail');
});
// console.log($scope.Site);
});
html:
{{$scope.Site}} //this will work, asynchronously get results after Success function
Console.log you are using outside is executed immediately when the controller is initialized,
So the $scope.Site is not have a value
but Success function is called after the data is returned from the server, so value is present in the Success function
I am trying to configure an oncomplete method for all ajax requests so that I can handle session timeout.
I tried adding the following script but it didn't work the same way as setting oncomplete property for p:ajax element. It wouldn't execute each time an Ajax request is made.
$.ajaxSetup({method: post,
complete: function(xhr, status, args){
var xdoc = xhr.responseXML;
if(xdoc == null){
return;
}
errorNodes = xdoc.getElementsByTagName('error-name');
if (errorNodes.length == 0) {
return;
}
errorName = errorNodes[0].childNodes[0].nodeValue;
errorValueNode = xmlDoc.getElementsByTagName('error-message');
errorValue = errorValueNode[0].childNodes[0].nodeValue;
alert(errorValue);
document.location.href='${pageContext.request.contextPath}/login/login.jsf';
}
});
Any help would be appreciated
PrimeFaces newer versions (using PF 5 here)
var originalPrimeFacesAjaxUtilsSend = PrimeFaces.ajax.Request.send;
PrimeFaces.ajax.Request.send = function(cfg) {
if (!cfg.oncomplete) {
cfg.oncomplete = doYourStuff;
}
originalPrimeFacesAjaxUtilsSend.apply(this, arguments);
};
Just to keep it somewhere, tried to find at stackoverflow but only older versions..
hope someone find this useful.
I managed to implement this by wrapping Primefaces AjaxUtils method.
var originalPrimeFacesAjaxUtilsSend = PrimeFaces.ajax.AjaxUtils.send;
PrimeFaces.ajax.AjaxUtils.send = function(cfg) {
if (!cfg.oncomplete) {
// register default handler
cfg.oncomplete = oncompleteDefaultHandler;
}
originalPrimeFacesAjaxUtilsSend.apply(this, arguments);
};
In primefaces there is component ajaxStatus which you can use for this purpose. Read documentation to see some more details about it, but for your use-case it can be something like this:
<p:ajaxStatus oncomplete="ajaxStatusHandler(xhr, status, args)"/>
and you can use your JavaScript function as is:
function ajaxStatusHandler(xhr, status, args) {
// your code ...
}
NOTE: this method can be used just for global AJAX requests (which is default in PrimeFaces), also, as I know, cross-domain script or JSONP (JSON with padding) requests can't be global.
In the function getweather() I do fetch some weather data from a Json-object and store that data in the variable data. Within that function, I can handle the Json-data, but how do I access the data from outside of getweather()?
It doesn't matter if i return the variable location or data. The variable place is just not Json.
How do I handle the place variable in order to make it work as it does within the function getweather()?
var ajax = require('ajax');
var place;
place = getweather();
function getweather()
{
// Make the request
ajax(
{
url: 'http://api.openweathermap.org/data/2.5/weather?q=paris.fr', type: 'json'
},
function(data)
{
// Success!
console.log("Successfully fetched weather data!");
// Extract data
var location = data.name;
return location;
},
function(error) {
// Failure!
console.log('Failed fetching weather data: ' + error);
}
);
}
The A in AJAX stands for asynchronous. What's happening in your code is that you are trying to assign a value to place before the asynchronous call to api.openweather.org has returned.
You can check this by placing a statement like console.log(place); directly after place = getweather();. You will notice in the console that None is returned before you see Successfully fetched weather data!
This issue has been described in detail in this post. It recommends restructuring your code around your callbacks.