I have a javascript below, which appends a DIV on page load and hides it after 3 sec.
var testObj = {
initialize: function() {
var that = this;
$(window).on("load", function() {
(function ($) { //Append Div
$('body').append("<div>TEST</div>");
})(jQuery);
that.hideAppendedDiv();
});
},
hideAppendedDiv: function() { //Hide appended Div after 3s
setTimeout(function(){
$("div").hide();
}, 3000);
}
};
//call Initialize method
testObj.initialize();
How to write Jasmine test cases for the methods in the code.
I'm guessing that you don't really want to test a Javascript function such as $(window).on('load')... , but that your own function hideAppendedDiv() get's called from $(window).on('load'). Furthermore, you want to make sure that the function hideAppendedDiv() works as well.
IMO, you need two expects.
Somewhere in your setup beforeEach function:
beforeEach(function () {
spyOn(testObj , 'hideAppendedDiv').and.callThrough();
});
Expectations
it('expects hideAppendedDiv() to have been called', function () {
// make the call to the initialize function
testObj.initialize ();
// Check internal function
expect(testObj.hideAppendedDiv).toHaveBeenCalled();
});
it('expects hideAppendedDiv() to hide div', function () {
// make the call to the hideAppendedDiv function
testObj.hideAppendedDiv();
// Check behavior
expect(... check the div ...)
});
Edit
Just to be clear, Jasmine executes all the expects in order. Now, if you have two functions fn_1(), and fn_2() and you want to test that they were called in order you can setup yet another spi function that returns a specific value, or a sequential and incremental set of values every time it is called.
beforeEach(function () {
spyOn(testObj , 'fn_1').and.returnValues(1, 2, 3);
spyOn(testObj , 'fn_2').and.returnValues(4, 5, 6);
});
The first time fn_1 is called it will return 1, respectively fn_2 will return 4.
That is just one of the ways, but you have to get creative when testing.
Now if you want to test that a function was called after x amount of time here is a post that already explains it.
You don't need to test the window load event, if you move the append code out of the anonymous function call and pass it into the event handler instead you can test the functionality in exactly the same way you would anything else and your code will be better structured.
Related
I would like to bind or chain a callback/function to another function.
There is a similar question (without any valid answer): jQuery , bind a callback to any function
But, for me I would not want to be limited only to jQuery realm.
I looking for a broader answer, I wonder if it's possible with vanilla javascript or library other than jQuery.
Example:
// a function which I don't want or I can't edit it's body
var thirdPartyObject = {
doSomething: function(args) {
// a lot of code
console.log('doing stuff: ' + args);
}
};
// my function
var easyCallback = function() {
// processing stuff
console.log('doing more stuff');
}
// the bind
magicLibrary.bind(thirdPartyObject.doSomething, easyCallback);
// run
thirdPartyObject.doSomething(1);
thirdPartyObject.doSomething(10);
When I run this "code", the following output represents the behaviour I'm looking for:
doing stuff: 1
doing more stuff
doing stuff: 10
doing more stuff
Is it possible?
EDIT: the bind is a conceptual term, maybe you think this like a chain, trigger or even another term.
But the import is the second function or callback which in my example is easyCallback() must be somehow connected to the first one doSomething().
And every time the doSomething() is called or executed I want the easyCallback() to be executed as well after the first is finished.
But the without "wrapping" them around or without rewriting the first one.
You would have to wrap the doSomething function inside yet another function, like so:
// a function which I don't want or I can't edit it's body
var thirdPartyObject = {
doSomething: function(args) {
// a lot of code
console.log('doing stuff: ' + args);
}
};
// my function
var easyCallback = function() {
// processing stuff
console.log('doing more stuff');
}
// the bind
// magicLibrary.bind(thirdPartyObject.doSomething, easyCallback);
const doSomething = thirdPartyObject.doSomething.bind(thirdPartyObject);
thirdPartyObject.doSomething = function(args) {
doSomething(args);
easyCallback(args);
};
// run
thirdPartyObject.doSomething(1);
thirdPartyObject.doSomething(10);
I have the following functions:
function getPersonData(id) {
retrieveData(
id,
function(person) {
if(person.name) {
displayPerson(person);
}
}
}
function retrieveData(id, successCallBack) {
executeRequest(id, {
success: successCallBack
});
}
getPersonData retrieves a person's information based on the id. It in turn calls retrieveData by passing in the id and a successCallBack function.
retrieveData takes the id and successCallBack and calls another function, executeRequest, which gets the data and passes back a person object.
I am trying to test getPersonData and have the following spec set up
describe("when getPersonData is called with the right person id", function() {
beforeEach(function() {
spyOn(projA.data, "retrieveData").and.returnValue(
{
'name': 'john'
}
);
spyOn(projA.data, "displayPerson");
projA.data.getPersonData("123");
});
it("displays the person's details", function() {
expect(projA.data.displayPerson).toHaveBeenCalled();
);
});
But when the spec is executed the displayPerson method isn't called. This is because the person data being passed back from the success callBack function(person) isn't being passed in even though I have mocked retrieveData to return a result.
My question is:
Is this the right way to test callBack functions? Either way what am I doing wrong?
Ok, so jasmine is tricky in a lot of subtle ways and I think there's two main issues with your code
You have way too many asynchronous calls wrapped in each other. Which is by itself not a problem, but it makes testing in JASMINE hell of a lot harder. For example, what is the point of having a retrieveData function which just calls executeRequest function with the exact same parameters but in a slightly different way.
I rewrote your getPersonData to be like this
function getPersonData(id) {
// this is needed to properly spy in Jasmine
var self = this;
//replaced retrieveData with just execute request
// self is required to properly spy in Jasmine
self.executeRequest(id, {
success: function(person) {
if (person.name) {
self.displayPerson(person);
}
}
})
}
//I don't know what exactly executeRequest does
//but I took the liberty to just make it up for this example
function executeRequest(id, callback) {
callback.success({
name: id
});
}
//I also assumed that projA would look something like this
var projA = {
data: {
getPersonData: getPersonData,
retrieveData: retrieveData,
displayPerson: displayPerson,
executeRequest: executeRequest
}
};
2. In order to test asynchronous code in Jasmine, you need to include a done callback with the test. Also, if you expect a callback function to fire automatically, you need to set it up within a setTimeout function, otherwise it will never fire. Here's an adjusted example:
describe("when getPersonData is called with the right person id", function() {
beforeEach(function() {
//you should not spyOn retriveData or executeCode because it will override the function you wrote and will never call displayPerson
// you should only have spies on the methods you are testing otherwise they will override other methods
spyOn(projA.data, "displayPerson");
});
it("displays the person's details", function(done) {
// it's better to call the function within specific test blocks so you have control over the test
projA.data.getPersonData("123");
// at this point, it will just have called the getPersonData but will not call executeRequest
setTimeout(function() {
//this block will just call executeRequest
setTimeout(function() {
//this block will finally call displayPerson
expect(projA.data.displayPerson).toHaveBeenCalled();
//tell jasmine the test is done after this
done();
})
})
});
})
Have problem under beforeEach function in my test class.
Click on the usersTab sometimes works fine, and sometimes returns StaleElementReferenceException.
Have tried protractor.ExpectedConditions like presenceOf or visibilityOf or elementToBeClickable but none worked in 100%.
I think that is caused by asynchronous and sometimes browser firstly want to click and afterwards wait - is it possible ?
Any idea how to handle it ?
var OnePage = require('../pages/one_page.js');
var SecondPage = require('../pages/second_page.js');
describe('Test', function () {
var onePage;
var secondPage;
var EC = protractor.ExpectedConditions;
beforeEach(function () {
browser.ignoreSynchronization = true;
onePage = new OnePage();
browser.wait(EC.presenceOf(onaPage.userLogin), 5000);
onePage.setUser('login#login');
onePage.setPassword('password');
onePage.login();
secondPage = new SecondPage();
browser.wait(EC.visibilityOf(secondPage.usersTab), 10000);
usersPage.usersTab.click();
});
I am using jasmine:2 and protractor:2.2.0 .
When I first started to build my Test Suite I ran into a similar issue and it took me a while to track down a solution. When you initialize the Page Object using a standard var in your beforeEach function it can hold on to an old instance when the 2nd or higher test is run. No idea how long it typically takes or what really triggered the hiccup in my tests, it was always random for me. The only way I could solve this was to use the this keyword in my beforeEach function like the example below. When the beforeEach function is executed the instance is correctly renewed in each test.
NOTE: In my conf.js file under onPrepare I have the following function setup to tell Protractor if the page that follows is an Angular page or not:
global.isAngularSite = function(flag) {
browser.ignoreSynchronization = !flag;
};
var OnePage = require('../pages/one_page.js');
var SecondPage = require('../pages/second_page.js');
describe('Test', function () {
beforeEach(function () {
isAngularSite(true);
this.onePage = new OnePage();
this.secondPage = new SecondPage();
this.ec = protractor.ExpectedConditions;
browser.wait(this.ec.presenceOf(this.onePage.userLogin), 5000);
this.onePage.setUser('login#login');
this.onePage.setPassword('password');
this.onePage.login();
browser.wait(this.ec.visibilityOf(this.secondPage.usersTab), 10000);
usersPage.usersTab.click();
});
it('', function() {
});
});
Try chaining the actions with each other so that they execute serially once the first one is completed resolving its promise. Here's how -
beforeEach(function () {
browser.ignoreSynchronization = true;
onePage = new OnePage();
browser.wait(EC.presenceOf(onaPage.userLogin), 5000).then(function(){
onePage.setUser('login#login');
onePage.setPassword('password');
onePage.login();
});
secondPage = new SecondPage();
browser.wait(EC.visibilityOf(usersPage.usersTab), 10000).then(function(){
usersPage.usersTab.click().then(function(){
//If you want to verify something after click, do it here.
browser.sleep(500);
});
});
});
If you want the second page actions to be done only after first one, then chain them together.
browser.wait(EC.presenceOf(onaPage.userLogin), 5000).then(function(){
//Actions of OnePage
}).then(function(){
//Actions of second page
secondPage = new SecondPage();
browser.wait(EC.visibilityOf(usersPage.usersTab), 10000).then(function(){
usersPage.usersTab.click().then(function(){
//If you want to verify something after click, do it here.
browser.sleep(500);
});
});
});
Hope this helps.
So I have a block of about 10 lines of code in JQuery. the lines are all independent, and when I execute them (I just type them in the console, its an automated test) i need them to be delayed form each other, like with 2 seconds difference. I started by using JavaScript setTimeout() on each line, but for 10 separate lines of code i assume there's a sexier way to do so... Also JQuery DELAY doesn't work since these aren't effects. Any ideas? here's the general idea of my code block..
$("#tag1").trigger("click");
$('#tag2').val("some text");
$("#tag3").trigger("keyup");
$('#tag4 select option[value="4"]').prop('selected',true);
$("#tag5").val(6);
$('#tag6').val(3).change();
$('#tag7').val(30).change();
$('#tag8').val("2017-06-29");
$('#tag9').val("2015-06-29");
$('#tag10').val("This is the test tasks' description.");
$(".id1").trigger("click");
$(".id2").val("buy oranges");
As you can see all the tags and ID are unique...
any idea would be greatly appreciated!
From my comment: An array of anonymous functions would be one option. Pass them recursively to a function that uses setTimeout Slicing off the first one each time..
e.g. something like this (the test just prints the functions to console as the DOM elements are missing):
JSFiddle: http://jsfiddle.net/bnq6tppb/1/
var operations = [
function () {
$("#tag1").trigger("click");
},
function () {
$('#tag2').val("some text");
},
function () {
$("#tag3").trigger("keyup");
},
function () {
$('#tag4 select option[value="4"]').prop('selected', true);
},
function () {
$("#tag5").val(6);
},
function () {
$('#tag6').val(3).change();
},
function () {
$('#tag7').val(30).change();
},
function () {
$('#tag8').val("2017-06-29");
},
function () {
$('#tag9').val("2015-06-29");
},
function () {
$('#tag10').val("This is the test tasks' description.");
},
function () {
$(".id1").trigger("click");
},
function () {
$(".id2").val("buy oranges");
}];
and use recursively with a timer:
function processoperations(ops){
// Run the first operation
ops[0]();
setTimeout(function(){
if (ops.length > 1){
// Go recursive after 2 seconds
processoperations(ops.slice(1));
}
}, 2000);
}
and start like this:
processoperations(operations);
This allows you to process any code at intervals. The alternative (as others mentioned) is an automated testing tool (like Selenium) if this is actually a testing problem. Update: You have mentioned you are not allowed to use Selenium.
Im not very good wit JS and I just dont get why this wont work!
The code uses jquery to apply the pulsate efect to one of my divs and run forever unless I stop it with another function, but I cannot figure our why my first piece of code wont run!
function animate(var x){
// Do pulsate animation
$(x).effect("pulsate", { times:4 }, 5000);
// set timeout and recall after 10secs
setTimeout(animate, 10000);
}
$(document).ready(animate("#mydiv"));
Only way to get it working is for me to do this
function animate(){
// Do pulsate animation
$("#mydiv").effect("pulsate", { times:4 }, 5000);
// set timeout and recall after 10secs
setTimeout(animate, 10000);
}
$(document).ready(animate);
Note that in the first snippet the code uses variables to be more useful and the second piece has the selectors name hardcoded
Don't use var in your function declaration. Just use:
function animate(x){
Also, you probably want something like this for your first example:
function animate(x){
return function () {
function animateInner() {
$(x).effect("pulsate", { times:4 }, 5000);
setTimeout(animateInner, 10000);
}
animateInner();
};
}
$(document).ready(animate("#mydiv"));
DEMO: http://jsfiddle.net/XHKbC/
Otherwise, the original animate("#mydiv") call executes immediately (and $(x) probably won't find anything since the DOM isn't ready yet). $(document).ready() expects a reference to a function. You called a function instead. But that's all a little overkill. Just use:
$(document).ready(function () {
animate("#mydiv");
});
but you'll have to change your function so the setTimeout passes the value of x as well:
function animate(x){
// Do pulsate animation
$(x).effect("pulsate", { times:4 }, 5000);
// set timeout and recall after 10secs
setTimeout(function () {
animate(x);
}, 10000);
}
DEMO: http://jsfiddle.net/XHKbC/2/
Although it's a little more code/complex, my first example doesn't suffer the problem in my second (having to pass x in the setTimeout) by using a closure.
UPDATE:
Being shown how you are using this code, I'd set it up like this:
function Animater(target) {
var self = this;
var animateTO;
var animateElement = target;
function animate() {
animateElement.effect("pulsate", { times:4 }, 5000);
animateTO = setTimeout(animate, 10000);
}
self.start = function () {
animate();
};
self.stop = function () {
animateElement.finish();
clearTimeout(animateTO);
};
}
And create a new one like:
var mydivAnimater = new Animater($("#mydiv"));
You can then call .start() and .stop() on it, and you create any number of these Animater objects on different elements as you want.
DEMO: http://jsfiddle.net/K7bQC/3/
Your code has two issues:
omit the var:
function animate(x){
modify your event handler:
$(document).ready(function(){
animate("#mydiv");
});
You need to hand over a function reference (either animate or function(){}), not run the code right away which you are doing if you pass animate().
Now to not lose the reference to your x you have to modify the animate call in the timeout too:
setTimeout(function () {
animate(x);
}, 10000);
You dont need to type var when specifying a function parameter.