I'm trying to automate end to end testing with protractor and I'm using sinonjs to setup a fakeServer to respond to certain ajax calls (not all) which are triggered by button clicks.
I'm stuck and not sure how to go about it and being a novice in automation I'm not sure if I'm on the right path.
var sinon = require('sinon');
describe("SinonFakeServerTest", function() {
var fakeServer;
beforeEach(function () {
fakeServer = sinon.fakeServer.create();
fakeServer.autoRespond = true;
var data = {key1: 'xyz', key2: 'abc'};
var response = [ 200, { "Content-Type": "application/json" }, data ];
fakeServer.respondWith( '/abc/xyz/*', response );
}
afterEach(function () {
fakeServer.restore();
}
it("should fake a ajax request", function () {
// click on this button triggers ajax call..
element(by.css('.take-button')).click();
//should show fake data on ui
});
});
This is the production code for button click controller and model
'.take-button click' : function(el, ev) {
model.getData(listParams, this.proxy('setUpData'));
},
getList : function(params, success) {
$.ajax({
url : '/abb/xyz/getAll.htm',
dataType : 'json',
type : "GET",
data : {
params : params
},
success : success
});
}
I didn't successfully fake server with Sinon's fakeServer feature when production code was using JQuery for AJAX calls.
I would suggest to try plain Sinon stubbing for $.ajax. There is actually example on sinonjs.org site (look into section Testing Ajax):
after(function () {
// When the test either fails or passes, restore the original
// jQuery ajax function (Sinon.JS also provides tools to help
// test frameworks automate clean-up like this)
jQuery.ajax.restore();
});
it("makes a GET request for todo items", function () {
sinon.stub(jQuery, "ajax");
getTodos(42, sinon.spy());
assert(jQuery.ajax.calledWithMatch({ url: "/todo/42/items" }));
});
I solved the above issue in protractor without sinonjs.
I used mockjax to hack selected ajax calls and injected this script through browser.executeScript()
Related
I am new to Vue and Axios and trying to use it in Salesforce Marketing Cloud - Cloud pages. Basically there are 3 parts,
HTML + vue page : this is a form page, where the user is asked to input the automation name and click on send button
App.js : this is build using axios and Vue.
Form-hander.js (backend) : SSJS code that runs the automation.
I referred this document to build this setup -https://ampscript.xyz/how-tos/how-to-start-status-of-automation-from-marketingcloud-form/. I understand the Form-hander.js (ssjs) code and this can be skipped.
What I am not able to understand is the flow of App.js, could anyone please explain me what is happening here.
I understand that on click of send button, the function in App.js - validateForm is called. Here after I don’t understand the flow of the code.
From App.js is the form-handler code called ? OR the post method used in the HTML page is directly called the form-handler page and staring the automation?
Here is the code of app.js. Can some explain to me in simple terms the flow of this code, would be really helpful.
new Vue({
el: '#app',
data: {
status: 100,
form: {
name: 'My Test Automation',
context: 'perform'
},
endpoint: '',
message: ''
},
watch: {
status: function () {
if(this.status == 201 || this.status == 102) {
this.form.context = 'check';
} else {
this.form.context = 'perform';
}
}
},
mounted: function() {
this.endpoint = this.$refs.form.getAttribute('action');
},
methods: {
sendFormData: function() {
this.status = 101;
var $this = this;
axios({
method: 'POST',
url: $this.endpoint,
data: $this.form,
validateStatus: function() { return true }
}).then(function(result) {
$this.status = result.data.Status;
$this.message = result.data.Message;
$this.checkStatus();
}).catch(function(error) {
console.error(error);
});
},
checkStatus: function() {
var $this = this;
var intervalID = setInterval(function() {
axios({
method: 'POST',
url: $this.endpoint,
data: $this.form,
validateStatus: function() { return true }
}).then(function(result) {
$this.status = result.data.Status;
$this.message = result.data.Message;
if($this.status == 200 || $this.status == 500) {
clearInterval(intervalID);
}
}).catch(function(error) {
console.error(error);
});
}, 10000);
},
validateForm: function() {
if (this.$refs.form.checkValidity() !== false) {
this.sendFormData();
}
this.$refs.form.classList.add('was-validated');
}
}
})
Let me explain you the flow of the code you posted :
Once component mounted, The first method which is getting called is mounted(). In this method you are fetching the endopint binded to the action attribute in your form html element and binding that in a data variable via this.endpoint.
Now, you are calling validateForm() method on click of submit button to validate the input fields. If validation pass, you are calling sendFormData() method to make an POST API call.
After getting the response, you added a watcher on status to update the form.context value based on the status code you received from an API response.
At the end, you are calling a checkStatus() method on success of axios call and in this checkStatus() method you are again making an POST API call after every 10 seconds and following step 3.
When the components is mounted, you run the form binded action on (submit?)
The action is probably binded to the sendFormData function(in methods)
Inside sendFormData, there is the setup of the axios request, followed be a then callback which handles the response from the request
The checkStatus function is called inside the "then" block
Sends the same data back to the server every 10 seconds if the previous response
doesn't have status code other than 200 or 500.
ValidateForm is may binded to some onInput or onChange event on the template
** The watcher is always looking for the status code and updates a form context
I am creating a website where the backend is node.js with express, and clientside is using mithril. We cannot use ajax, form and jquery.
My question is how can we communicate from mithril js file to express? Let me give you example:
app.post("/testFunction",function(req,res){
//Pass result
});
In mithril:
m.render(document.body, [
m('input[type=text]'),
m('button'),
m('span', 'show the result')
]);
Here I have an input box and a button. When i click on button it should call my function in express, save data and return the message to be displayed in a span.
Any ideas?
You need to use m.request to make a post to your endpoint. Check out the docs here (http://mithril.js.org/request.html). Here's a quick example.
m.render(document.body, [
m('input[type=text]#testInput'),
m('button', {
onclick: function() {
document.getElementById("testInput").value
m.request({
method: "PUT",
url: "http://127.0.0.1:3000/testFunction",
data: {
inputValue: document.getElementById("testInput").value
}
})
.then(function(response) {
console.log(response);
})
}
}, "Send Request")
]);
I've had no problems sorting out mocking the success condition, but cannot seem to fathom how to mock the failure/timeout conditions when using Sinon and Qunit to test and ajax function:
My set up is this:
$(document).ready( function() {
module( "myTests", {
setup: function() {
xhr = sinon.sandbox.useFakeXMLHttpRequest();
xhr.requests = [];
xhr.onCreate = function (request) {
xhr.requests.push(request);
};
myObj = new MyObj("#elemSelector");
},
teardown: function() {
myObj.destroy();
xhr.restore();
}
});
});
and my success case test, running happily and receiving/passing through the received data to the success method is this:
test("The data fetch method reacts correctly to receiving data",
function () {
sinon.spy(MyObject.prototype, "ajaxSuccess");
MyObject.prototype.fetchData();
//check a call got heard
equal(1, xhr.requests.length);
//return a success method for that obj
xhr.requests[0].respond(200, {
"Content-Type": "application/json"
},
'[{ "responseData": "some test data" }]'
);
//check the correct success method was called
ok(MyObj.prototype.ajaxSuccess.calledOnce);
MyObj.prototype.ajaxSuccess.restore();
}
);
However, I cannot work out what I should be putting instead of this:
xhr.requests[0].respond(200, { "Content-Type": "application/json" },
'[{ "responseData": "some test data" }]');
to make my ajax call handler hear a failure or timeout method? The only thing I could think to try was this:
xhr.requests[0].respond(408);
But it doesn't work.
What am I doing wrong or what have I misunderstood? All help much appreciated :)
For the timeout, sinon’s fake timers could help. Using them you wouldn’t need to set the timeout to 1ms. As for the failures, your approach looks correct to me. Can you give us more code, especially the failure handler?
Doing something like this
requests[0].respond(
404,
{
'Content-Type': 'text/plain',
'Content-Length': 14
},
'File not found'
);
works to trigger the 'error' callback in jQuery AJAX requests.
As for the timouts, you can use sinons fake clock like this:
test('timeout-test', function() {
var clock = sinon.useFakeTimers();
var errorCallback = sinon.spy();
jQuery.ajax({
url: '/foobar.php',
data: 'some data',
error: errorCallback,
timeout: 20000 // 20 seconds
});
// Advance 19 seconds in time
clock.tick(19000);
strictEqual(errorCallback.callCount, 0, 'error callback was not called before timeout');
// Advance another 2 seconds in time
clock.tick(2000);
strictEqual(errorCallback.callCount, 1, 'error callback was called once after timeout');
});
The main idea that I would use is to wrap everything related to the request inside "another function" that returns a promise.
Then in the test, when I mock the "another function" I just return a Promise.reject({}).
If some endpoint is going to give me a timeout, that is equivalent to a failed promise.
Set a timeout on your $.ajax() call and use Sinon fake timers to move the clock ahead before responding.
I am new to Backbone.js and Require.js I am trying to get data from the server and store it in my backbone model. The problem is the data is not populating the model.
require.config({
paths:{
jquery:"libs/jquery-1.7.1.min",
underscore:"libs/underscore-min",
backbone:"libs/backbone-min",
models:"models",
views:"views",
collections:"collections"
},
shim:{
"backbone":{
"deps":["underscore","jquery"],
"exports":"Backbone"
},
"underscore":{
"exports":"_"
}
}
});
require (['routes'],function() {
new Router ();
});
The Model
define (['jquery','underscore','backbone'],function($,_,Backbone){
Video = Backbone.Model.extend ({
defaults:{
video_id:'',
video_name:'',
},
urlRoot:"/video",
});
});//end define
The Router
define(['backbone','models/Video'],function(Backbone){
Router = Backbone.Router.extend({
routes:{
"/":"index",
},
initialize:function (){
this.index ();
},
index:function ()
{
video = new Video ({id:"1Uw6ZkbsAH8"});
video.fetch();
console.log (video.toJSON());
}
});
});//end define
I check the network response and I am getting "{video_id:1,video_name:test_backbone}" as a response but when I console.log (video.toJSON()) i get - Object {id: "1Uw6ZkbsAH8", video_id: "", video_name: ""}. What am I doing wrong?
in your code logging is done right after the fetch is called. fetch makes an asycnhronous request to server for data. Your logging call is executed right away even before the network request is completed. this is one of the gotchas of event-based programming.
try calling console.log in the success callback.
video.fetch ( {
success: console.log(video.toJSON());
}
);
I'm about to make a web app which will have a pretty heavy client end. I'm not sure about the way to organize my javascript code, but here is a basic idea :
// the namespace for the application
var app = {};
// ajax middle layer
app.products = {
add : function(){
// send ajax request
// if response is successful
// do some ui manipulation
app.ui.products.add( json.data );
},
remove : function(){},
...
};
app.categories = {
add : function(){},
....
};
// the ui interface which will be called based on ajax responses
app.ui = {};
app.ui.products = {
add : function( product_obj ){
$('#products').append( "<div id='"+product_obj.id+"'>"+product_obj.title+"</div>" );
}
};
app.ui.categories = {};
Anybody got similar experiences to tell me the pros and cons of this approach? What's your way of designing client side javascript code architecture? Thanks.
[update] : This web app, as you see from the above, deals with products CRUD, categories CRUD only in a ajax fashion. I'm only showing an snippet here, so you guys know what I'm trying to achieve and what my question is. Again, I'm asking for inputs for my approach to organize the code of this app.
That is similar to the way I do my JavaScript projects. Here are some tricks I have used:
Create one file for each singleton object. In your code, store ajax, middle layer and ui interface in separate files
Create a global singleton object for the 3 layers usually in the project; GUI, Backend and App
Never use pure ajax from anywhere outside the Backend object. Store the URL to the serverside page in the Backend object and create one function that uses that URL to contact the server.
Have a JSON class on the server that can report errors and exceptions to the client. In the Backend object, check if the returned JSON object contains an error, and call the serverError function in the GUI class to present the error to the user (or developer).
Here is an example of a Backend object:
var Backend = {};
Backend.url = "/ajax/myApp.php";
Backend.postJSON = function(data, callback){
var json = JSON.stringify(data);
$.ajax({
type: "POST",
url: Backend.url,
data: "json="+json,
dataType: "json",
success: function(response){
if(response){
if(response.task){
return callback(response);
}else if(response.error){
return Backend.error(response);
}
}
return Backend.error(response);
},
error: function(response){
Backend.error({error:"network error", message:response.responseText});
},
});
};
Backend.error = function(error){
if(error.message){
Client.showError(error.message, error.file, error.line, error.trace);
}
};
This can be improved by storing the ajax object somewher in the Backend object, but it's not necessary.
When you build something non trivial, encapsulation is important to make things maintainable in long run. For example, JS UI is not just simple JS methods. A UI components consists of css, template, logic, localization, assets(images, etc).
It is same for a product module, it may need its own settings, event bus, routing. It is important to do some basic architectural code in integrating your chosen set of libraries. This had been a challenge for me when I started large scale JS development. I compiled some best practices in to a reference architecture at http://boilerplatejs.org for someone to use the experience I gained.
For client-side ajax handling I have a URL object that contains all my urls and than I have an ajax object that handles the ajax. This is not a centric approach.In my case I have I have different urls handling different tasks. I also pass a callback function to be executed into the ajax object as well.
var controller_wrapper = {
controller: {
domain: "MYDOMAIN.com",
assets: "/assets",
prefix: "",
api: {
domainer: "http://domai.nr/api/json/info",
tk_check: "https://api.domainshare.tk/availability_check"
},
"perpage": "/listings/ajax",
"save_image": "/members/saveImage",
"update": "/members/update",
"check_domain": "/registrar/domaincheck",
"add_domain": "/registrar/domainadd",
"delete_listing": "/members/deactivateProfile",
"save_listing": "/members/saveProfile",
"get_images": "/images/get",
"delete_image": "/images/delete",
"load_listing": "/members/getProfile",
"load_listings": "/members/getListings",
"loggedin": "/members/loggedin",
"login": "/members/login",
"add_listing": "/members/add",
"remove": "/members/remove",
"get": "/members/get",
"add_comment": "/members/addComment",
"load_status": "/api/loadStatus"
}
}
var common = {
pager: 1,
page: 0,
data: {
saved: {},
save: function (k, v) {
this.saved[k] = v;
}
},
ajax: {
callback: '',
type: 'POST',
url: '',
dataType: '',
data: {},
add: function (k, val) {
this.data[k] = val;
},
clear: function () {
this.data = {};
},
send: function () {
var ret;
$.ajax({
type: this.type,
url: this.url,
data: this.data,
dataType: this.dataType !== '' ? this.dataType : "json",
success: function (msg) {
if (common.ajax.callback !== '') {
ret = msg;
common.ajax.callback(ret);
} else {
ret = msg;
return ret;
}
return;
},
error: function (response) {
console.log(response);
alert("Error");
}
})
}
}
var callback = function (results) {
console.log(results
}
common.ajax.callback = callback;
common.ajax.type = "jsonp";
common.ajax.type = "POST";
common.ajax.url = controller_wrapper.controller.perpage;
common.ajax.add("offset", common.page);
common.ajax.add("type", $("select[name='query[type]']").val());
common.ajax.add("max", $("select[name='query[max]']").val());
common.ajax.add("min", $("select[name='query[min]']").val());
common.ajax.add("bedrooms", $("select[name='query[bedrooms]']").val());
common.ajax.add("sort", $("select[name='query[sort]']").val());
common.ajax.send();