Qunit test alternates between pass and fail on page refresh - javascript

I have a two tests that are causing side effects with each other. I understand why as I am replacing a jQuery built-in function that is being called internally in the second test. However what I don't understand is why the test alternately passes and fails.
This question is similar However, I am not doing anything directly on the qunit-fixture div.
Here are my tests
test('always passing test', function() { // Always passes
var panelId = '#PanelMyTab';
var event = {};
var ui = {
tab: {
name: 'MyTab',
},
panel: panelId,
};
$('<div id="' + panelId + '">')
.append('Test')
.append('Show Form')
.appendTo('#qunit-fixture');
jQuery.fn.on = function(event, callback) {
ok(this.selector == panelId + ' .export', 'Setting export click event');
equal(callback, tickets.search.getReport, 'Callback being set');
};
loadTab(event, ui);
});
test('alternately passing and failing', function() { // Alternates between passing and failing on page refresh
expect(5);
var testUrl = 'test';
$('<div class="ui-tabs-panel">')
.append('Get Report')
.append('<form action="notest" target="" class="ticketSearch"></form>')
.appendTo('#qunit-fixture');
// Setup form mocking
$('form.ticketSearch').submit(function() {
var urlPattern = new RegExp(testUrl + '$');
ok(urlPattern.test($(this).prop('action')), 'Form action set to link href');
equal($(this).prop('target'), '_blank', 'Open form on a new page');
});
var event = {
target: 'a#getReport',
};
var result = getReport(event);
var form = $('form.ticketSearch');
ok(/notest$/.test($(form).prop('action')), 'Making sure action is not replaced');
equal($(form).prop('target'), '', 'Making sure that target is not replaced');
ok(false === result, 'click event returns false to not refresh page');
});
The tests will start off passing but when I refresh they will alternate between passing and failing.
Why is this happening? Even adding GET parameters to the url result in the same behavior on the page.
In the failing cases, the test is failing because internal jQuery is calling .on() when the submit() handler is set. But why isn't the test always failing in that case? What is the browser doing that a state is being retained during page refresh?
Update:
Here is the code that is being tested:
var tickets = function() {
var self = {
loadTab: function(event, ui) {
$(panel).find('.export').button().on('click', this.getReport);
},
search: {
getReport: function(event) {
var button = event.target;
var form = $(button).closest('div.ui-tabs-panel').find('form.ticketSearch').clone(true);
$(form).prop('action', $(button).prop('href'));
$(form).prop('target', '_blank');
$(form).submit();
return false;
}
}
};
return self;
}();

I've modified #Ben's fiddle to include your code with both of your tests. I modified some of your code to make it run correctly. When you hit the run button all of the tests will pass. When you hit the run button again, the second test ("alternately passing and failing") will fail -- this is basically simulating your original issue.
The issue is your first test ("always passing test") alters the global state by replacing the jQuery.fn.on function with an overridden one. Because of this, when the tests are run in order, the second test ("alternately passing and failing") uses the incorrect overridden jQuery.fn.on function and fails. Each unit test should return the global state back to its pre-test state so that other tests can run based on the same assumptions.
The reason why it's alternating between pass and fail is that under the hood QUnit always runs failed tests first (it remembers this somehow via cookie or local storage, I'm not exactly sure). When it runs the failed tests first, the second test runs before the first one; as a result, the second test gets jQuery's native on function and works. When you run it a third time, the tests will run in their "original" order and the second test will use the overridden on function and fail.
Here's the working fiddle. I've add the fix to "un-override" the on function after the test by caching the original var jQueryOn = jQuery.fn.on; function and resetting it at the end of the test via: jQuery.fn.on = jQueryOn;. You can probably better implement this using QUnit's module teardown() method instead.
You can check out https://github.com/jquery/qunit/issues/74 for more info.

I'm not sure I can solve this without some more info, but I can point out some possible issues.
The first test seems to have invalid syntax on line 2
var panelId = '#PanelMyTab');
But that's probably a type mistake, seeing as you say the first always passes.
I'm assuming that for the first test to pass(and be valid) the loadTab(event,ui) must run the jQuery.fn.on(), without it no assertions have been run. Which doing some testing with jQuery UI Tabs, seems to be the case (just not sure if it was your intention).
I'm not sure it's advisable putting these assertions within that function, and you must understand that you have overwritten the jquery function with a function that doesn't do anything, so it's likely to cause issues.
You seem to be doing something similar in the second test, you are expecting 5 assertions, but I can only see how the final 3 can be run
ok(/notest$/.test($(form).prop('action')), 'Making sure action is not replaced');
equal($(form).prop('target'), '', 'Making sure that target is not replaced');
ok(false === result, 'click event returns false to not refresh page');
The other 2 are within a submit function that doesn't look like it is invoked as part of the test.
Remember these tests are synchronous so it won't wait for you to hit submit before running the test and failing.
Here is an example
test('asynchronous test', function() {
setTimeout(function() {
ok(true);
}, 100)
})
Would fail as the ok is run 100ms after the test.
test('asynchronous test', function() {
// Pause the test first
stop();
setTimeout(function() {
ok(true);
// After the assertion has been called,
// continue the test
start();
}, 100)
})
The stop() tells qunit to wait and the start() to go!
There is also a asyncTest() detailed in the api here
Finally, it seems like you are trying to debug your code with these tests. It would be much easier to use chrome developer tools or firebug in firefox to set breakpoints on your code, and use console.log() and console.dir() to output information.
That being said I have no idea how it works for you at all, so I could be missing something :) If you're still stuck, see if you can add some more of the surrounding code and what your trying to achieve. Hope this helps.
PS: there is also a }; at the end which is invalid in the code you have given us, probably relevant in the actual application though ;)

Related

How to unit test onBeforeRendering of SAPUI5 control?

Let's assume that I have the following code:
var title = new sap.m.Title();
title.addEventDelegate({
onBeforeRendering: jQuery.proxy(function () {
title.setText(this._createTitleText());
}, this)
});
which sets the text to the title only on before rendering.
The question is how to unit test this?
As I understand SAPUI5 framework will fire this event and then I should be able to attach to it and verify that the text of the title is correct, but how to do it and is it a right way?
This is what I was trying, but at least I don't get any text from my control plus for me it is not clear which data I should pass to this method.
title.attachBeforeRendering("data", function (oEvent) {
var text = title.getText();
});
I found the following way:
title.addEventDelegate({
onBeforeRendering: jQuery.proxy(function () {
assert.equal(title.getText(), expectedTitle, "Title has correct text");
}, this)
});
However, this is not enough because onBeforeRendering will happen after the test which means that it depends on the time thus it is not sure if it is going to happen. But there is a way to make it for sure:
In the end of the test we should call:
return new Promise(function (resolve, reject) {
setTimeout(function () {
QUnit.start();
}, 100);
});
which will cover our case plus continue executing to another unit test.
You can try to call onAfterRendering manually or just invalidate or rerender the control.
Try to do something like
// act
oTitle.onAfterRendering();
// assert
assert.strictEqual(oTitle.getTitle(), "__expected_title__", "Title is correct!");
In case you are just testing if the title is changed. I would prefer to stub the setTitle method and check if it is called with a correct parameter.
You can see that usually events are tested in this way e.g.
https://github.com/SAP/openui5/blob/master/src/sap.m/test/sap/m/qunit/MultiComboBox.qunit.html#L4479
In your case you don't need a fake event, but it is quite similar.

Testing event handlers

I have an event handler that modifies some global variable based on the click action. How can I test it? For example:
function initEvent() {
function enable() {
var arr = Context.get('arr');
arr.push('aaa');
};
function disable() {
var arr = Context.get('arr');
arr.push('bbb');
};
$('#content').on('click', '#element', function () {
if (this.checked) {
enable();
} else {
disable();
}
});
};
This is the function I am calling after the HTML has been rendered. It calls enable() and disable() functions based on the user action. I want to test these functions and check if they behave correctly. How can I do that?
You want to test your code. You should never test code with console.log or alert. These are great to debug something on the fly, but they are not test tools. These promote manual testing, where you need to manually run the code and see that pass, that's horrible time waste.
You should use Jasmine in this case (you can use other testing frameworks, though Jasmine is super easy) to test your code. You can setup browser tests or headless tests, which is out of the scope of this question, there are tons of tutorials on the subject.
Now, in order to test this code, I assume that the Context has a static method get which returns an array which is on the Context IIFE scope. If the case is different feel free to fiddle around with the specs and make it serve your needs, or alternatively if you get stuck, update this question or ask another one on Stackoverflow.
I have setup Jasmine, with jasmine-fixture to test your code, the jQuery click event behavior. In this plunk you will find everything you need.
I am using the browser to test the code, so I need jasmine's html reporter.
The real tests are in script-spec.js, where I am using Jasmine's API, by describing a spec suite (with describe) and defining each spec with it method.
In beforeEach I prepare the code to run before each spec executes. Essentially here, I create a simple div with #content id and a child input element of type checkbox with #element id. I do this by using the
setFixtures('<div id="content"><input type="checkbox" id="element" /></div>');
Which is a method jasmine-fixture library provides.
Now I can test the code, wiring up the specs:
it("Should return an array with 'aaa' element when #element is checked", function() {
// Arrange
initEvent();
var checkbox = $("#content").find("#element");
// Act
checkbox.click();
// Assert
expect(Context.get('arr').length).toBe(1);
expect(Context.get('arr')).toEqual(['aaa']);
});
I run the initEvent method and get a reference of the checkbox element. In Act section I click the element manually, marking it as checked, which is the normal behavior. In Assert, I test the Context.get('arr') return value.
Again, link to plunk is here.
Hope this helps.
One simple test you can do to test enable, disable and the click handler is to create a function that checks the contents of arr in Context, and call it after each of the functions within the click handler that add something to arr.
The general way to test conditions in your code is with assertions which will throw an error if the condition you pass into them is false. You can use console.assert just for that:
$('#content').on('click', '#element', function() {
if (this.checked) {
enable();
// assert last element in `arr` is the enabled string 'aaa'
console.assert(
Context.get('arr')[Context.get('arr').length - 1] === 'aaa',
'enable() works'
);
} else {
disable();
// assert last element in `arr` is the disabled string 'bbb'
console.assert(
Context.get('arr')[Context.get('arr').length - 1] === 'bbb',
'disable() works'
);
}
});
If any of the tests run after you click your element, you know initEvent assigned the click handler and it works. Then, you just toggle the checked flag to test enable()/disable() as well.
If there are no errors in your browser console, the tests have passed. Otherwise, there will be an error in your console containing the message passed as the second argument to console.assert.
You could even make a helper function to simplify the testing a bit:
function assertLastElementInContextArr(elem, msg) {
var arr = Context.get('arr');
// assert last item in `arr` is equal to `elem`
console.assert(arr[arr.length - 1] === elem, msg);
}
$('#content').on('click', '#element', function() {
if (this.checked) {
enable();
// assert last element in `arr` is the enabled string 'aaa'
assertLastElementInContextArr('aaa', 'enable() works');
} else {
disable();
// assert last element in `arr` is the disabled string 'bbb'
assertLastElementInContextArr('bbb', 'disable() works');
}
});
EDIT based on your comment
But how do I mock the click event? I mean, I want to automatically test all those events, no I have to somehow trigger the click automatically. How do I do that?
If you want to programmatically invoke click events, you can use JS to trigger them in code. Since you're using jQuery, it already comes with a method trigger to do just that.
All you need to do is:
$('#content').trigger('click')
And it will activate your click handler and run the assertions tests from above.
In fact, jQuery even comes with aliased handlers for specific events so you can just do:
$('#content').click();
To automate the testing, you can create a function that will trigger the clicks and set the checked state as well, to test both cases.
function test(checked) {
var elem = $('#content');
elem.prop('checked', checked);
elem.click();
}
Important thing to be careful about is that these events will happen asynchronously so you must do something to manage a proper testing order if you're going to trigger multiple clicks. Otherwise you will set checked to true, trigger the click and then run the second test that will set checked to false before the click events even happen.
For demonstration purposes, here's one way to safely test multiple successive clicks by adding an event handler just for testing and removing it once you're done. One requirement for this to work is to attach the handler after all your other handlers have been attached, to make sure the test handler runs last. Additionally, you can run your assertions here as well to not pollute your code and keep the testing fully separated:
function test(checked, value, msg, done) {
var elem = $('#content');
elem.prop('checked', checked);
// attach a test event handler and trigger the click
elem.on('click', testClick);
elem.click();
// once the click is executed,
// remove the test handler,
// run the assertions and then
// call the callback to signal the test is done
function testClick() {
elem.off('click', runTest);
assertLastElementInContextArr(value, msg);
done();
}
}
// run your code before the tests
initEvent();
// test enable(), then once that's done, test disable()
test(true, 'aaa', 'enable() works', function() {
test(false, 'bbb', 'disable() works', function() {
console.log('All tests finished');
});
});
If you're going to be testing your entire app like this, you'd probably want to use a test framework like QUnit, Mocha, Jasmine which will handle all these async issues for you and give you a nice API to work with.
Just add console.log(<some variable>) or alert(<some variable>) at function calls. e.g.:
function initEvent() {
function enable() {
alert("enable called!");
var arr = Context.get('arr');
arr.push('aaa');
};
function disable() {
alert("disable called!");
var arr = Context.get('arr');
arr.push('bbb');
};
$('#content').on('click', '#element', function () {
alert("click occured!");
if (this.checked) {
enable();
} else {
disable();
}
});
};
Or use your browsers developer tools setting breakpoints at these spots.

Proper way to limit wait time on selenium element search

My nightwatch/selenium test code looks for elements in the page that may not exist using code such as
browser.elementIdElement(ELEMENT,'class name', 'myclass', function(r)
{ if (r.status == 0) console.log('found match')
else console.log('did not find match')
})
If the element is found, the callback is invoked quickly (< 50ms), but if no element matches, the callback takes much longer (>1000ms). I have to do this hundreds of times and there are only a few elements that match the search criteria, so it adds a significant amount of time to a test run.
I would like to limit the time selenium spends searching for elements. I tried using the selenium timeoutsImplicitWait() function, e.g.,
browser.timeoutsImplicitWait(250)
.elementIdElement(ELEMENT,'class name', 'myclass', function(r)
{...})
but it doesn't affect performance. What is the correct method for limiting element search time?
Perhaps I am misunderstanding your problem; both of these patterns works well for me:
client
.useXpath().waitForElementPresent(selector, this.timeout)
.useCss().waitForElementPresent(selector, this.timeout)
this.timeout is set in the prototype of the base test case.
util.inherits(MyExampleBaseClass, Base);
MyExampleBaseClass.prototype = {
before: function (client) {
// call super-before
Base.prototype.before.call(this, client);
this.timeout = 250;
},
after: function (client, callback) {
// call super-after
Base.prototype.after.call(this, client, callback);
},
// Note: This method will not be mistaken by nightwatch for a step because
// it is not enumerable (since it's on the prototype)
getSiteURL: function () {
return "http://www.urlundertest.com/";
}
};
The following code for checking the visibility and continue even if there is no match
browser.waitForElementVisible('selector',timeout,false);
or this for the existence :
browser.waitForElementPresent('selector',timeout,false);
According to nightwatch api,
By the default if the element is not found the test will fail. Set this to false if you wish for the test to continue even if the assertion fails.To set this globally you can define a property abortOnAssertionFailure in your globals.
For more detailed explanation, check here:
http://nightwatchjs.org/api/#waitForElementVisible

assertExists with if-else statement

I use this code:
var x = require('casper').selectXPath;
...
casper.waitForSelector(x("//a[contains(#id,'cell_13_1')]"), function() {
this.test.assertExists(x("//a[contains(#id,'cell_13_1')]"), 'Clickable');
this.click(x("//a[contains(#id,'cell_13_1')]"));
});
I am trying to use if-else with assertExists to click another element if the first is not there:
casper.waitForSelector(x("//a[contains(#id,'cell_13_1')]"), function() {
if(this.test.assertExists(x("//a[contains(#id,'cell_13_1')]")==="PASS"){
this.click(x("//a[contains(#id,'cell_11_1')]"));}
else{
this.click(x("//a[contains(#id,'cell_22_1')]"));
}
});
But that does not seem to work. How would one do it correctly?
That's exactly what casper.exists() is for. You can also explicitly pass or fail some things:
casper.waitForSelector(x("//a[contains(#id,'cell_13_1')]"), function() {
if(this.exists(x("//a[contains(#id,'cell_13_1')]")){
this.test.pass("Clickable");
this.click(x("//a[contains(#id,'cell_11_1')]"));
} else {
this.test.fail("Clickable");
//this.click(x("//a[contains(#id,'cell_22_1')]"));
}
});
This code is equivalent to your first snippet. Comment the fail() call and uncomment the last click in order to get your "intended" behavior.
Btw, it doesn't make sense to fail some assertion and still continue with the script. You have to think about what exactly you want to test and what part of your script is supposed to be navigation to the component under test.

Qunit error: assertion outside test context

I've searched all over and it appears this error is due to not using asyncTest properly. However, per the documentation, it appears that I am doing it correctly. I'm guessing I'm missing a small detail somewhere and need an extra pair of eyes...
I'm trying to test some code that makes an ajax request to get a page and then loads it in a lightbox. lightbox-content does not show up in the DOM until after the ajax call has completed and can be displayed. So, I can only check for it in my onComplete call back, which is where I have my test to see if it loaded it correctly.
Here is my code:
asyncTest('mytest', 1, function() {
utils.lightbox.show('/login', {
onComplete: function() {
ok($('#lighbox-content').is(':visible'), 'Lightbox loaded the /login page.');
start();
}
});
});
I get the error:
Uncaught Error: assertion outside test context, was at HTMLDivElement.window.utils
Can anyone see where I'm going wrong?
I agree that your code matches the documentation as far as I can tell.
Update
Even though the documentation doesn't show it, I wonder if you must tell QUnit to stop at some point so it knows to wait after the test function returns. I would think that QUnit assumes this since it's an async test, but it's worth a shot.
asyncTest('mytest', 1, function() {
stop();
...
});
I've been using Sinon.JS to avoid making the AJAX calls in the first place. This has three immediate benefits:
I don't depend on a server to respond to the requests.
I can specify different results for each test.
The tests run much faster.
The mocking can be done at the XMLHttpRequest level or on the jQuery method and is quite easy. Here's an example from one of my tests:
module("geo", {
setup: function () {
this.server = sinon.fakeServer.create();
},
teardown: function () {
this.server.restore();
}
}
test("returns detected ZIP code", function () {
this.server.respondWith("/geo/detect-zip-from-ip",
[ 200, { "Content-Type": "text/html" }, '90210' ]);
geo.detectZip(function (zip) {
assertThat(zip, is('90210'));
});
this.server.respond();
});
I have found a solution for my case, hope your problem has the same source.
Explaining in words:
I have a complicated asynchronous test
I have delayed events, and there are ok and equal assertions inside
Of course, all this is wrapped inside asyncTest
But, when the test is "completed" and I call start(), the event handlers remain there
After calling start(), all further calls of ok inside that asyncTest become illegal
And throw exceptions
I wonder what happens if the number in expect(in your example it's the second parameter) is exceeded. The same exception?
Explaining in code:
asyncTest('mytest', /*1,*/ function() {
function imgLoadedOrFailed (result) {
clearTimeout(imageTimeToLive);
img.off();
ok(result, 'Image in carousel pane has been loaded');
}
var imageTimeToLive = setTimeout(
imgLoadedOrFailed.bind(this, false),
5000),
img = panes[index].find('img:first');
if (img) {
img.on('load', imgLoadedOrFailed.bind(this, true));
img.on('error', imgLoadedOrFailed.bind(this, false));
}
});
// at some point I call: start();
In this example, when I "finish" the test calling start(), the onload and onerror events can still happen.

Categories

Resources