Handling errors in separate domains during testing - javascript

I have to listen to an event and check that a particular value is set after this event. For example:
eventEmitter.on("event", () => {
expect(thing).to.equal(otherThing);
done();
});
This all works fine if thing equals otherThing which it should -- however, if there is something wrong and the test fails (the two are not equal) or there is an error in this callback (I wrote otherTing instead of otherThing for example) the error is swallowed and done is not called.
I understand that the issue here is that the it spec code and the event callback run in separate domains so the errors in the latter do not propagate to the former.
What is the proper way to handle this situation in tests?

Related

How to error handle in cypress for element not found

The below block successfully executes when tr is found, but sometimes it will be empty and no tr. How to handle the exception if tr not found?
cy.get('tbody.ant-table-tbody tr').then((rows) => {
// success
});
There is no catch block chained here.
cy.get() will always fail if it cannot find the element (unless you run cy.get('foo').should('not.exist'), but that can't be used in combination with if/else.)
You can instead use JQuery in combination with Cypress to check for an element's existence without failing a test. In this case, we'll yield the result from the parent element, and search it with JQuery.
cy.get('tbody.ant-table-tbody').then(($parent) => {
if ($parent.find('tr').length) { // check if the length is > 0
// Code to run if tr is found
} else {
// Code to run if tr is not found
}
});
All of that being said, I would push back on needing to use something like this. Tests should be deterministic, and you should know before the test runs if tr exists or not. Consider the ways that you can pre-determine before the test runs (maybe seeding a database, or intercepting a network request). Also consider, what happens if tr never exists -- that functionality is never being tested. Or, if tr always exists, then the functionality of when it doesn't exist is never being tested.
There is an add-on package cypress-if written by former lead engineer at Cypress that addressed this problem.
The syntax is simple, just chain .if() and eveything after it only runs if the element is found.
cy.get('tbody.ant-table-tbody tr')
.if() // checks for 4 seconds
.then((rows) => {
// success
})
.else()
.then(() => {
//no rows
})
.finally(() => {
// either way
})
It will suppress the error that usually occurs when the element is missing.
It will use Cypress retry for asynchronous element loading, will will not happen if you use the jQuery method.
Don't do this if rows are fetched asynchronously
cy.get('tbody.ant-table-tbody').then(($parent) => {
// This will evaluate immediately and fail if the row is still loading
if ($parent.find('tr').length) {
...
But cypress-if is broken in Cypress v12
Cypress v12 split cypress commands into "commands" and "queries" (cy.get() is a query) and blocked overwriting of queries. This breaks a whole lot of prior code, including cypress-if.
Expect the change to be reversed before long.
Please see issue Can we please overwrite query commands #25078

Set timeout for Cypress expect assertion

My issue is that I'd like to have a timeout on the expect assertion from Cypress's library but can't seem to figure out a method to do so.
I've tried greatly increasing the global timeout in cypress.conf; however, that didn't work.
if ($widget.find('report-widget')) {
SVGCount++;
expect(barGraphsChange[index]).to.not.equal(undefined)
console.log(barGraphsChange, index)
cy.getBarGraphTotal(SVGCount, barGraphsChange[index])
}
If not a timeout then a reasonable workaround would be nice as well, Thanks!
Also a note: barGraphsChange[index] is in the process of getting calculated and assigned from a called custom Cypress command earlier on during this phase.
You can customise your expectations with should(), and this will automatically retry your assertions for either the specified timeout, or the global defaultCommandTimeout if none provided.
In your case I could probably imagine wrapping your object, specify a timeout and then passing your logic to should:
cy.wrap(someFunction, {timeout: 25000}).should(someFunction => expect(someFunction()).to.eq(someValue))
Read more here: https://docs.cypress.io/api/commands/should.html#Function

Javascript structured error handling

I am developing my first Javascript app and I am trying to go object oriented.
There is a basic closure that returns my primary object and every function I invoke rests in that object. Some pseudo code would look like this:
primary = (function(){
var object = {
doSomething = function(){};
},
return {intance:function(return object)}
});
//invocation
primary.instance().doSomething();
What I am trying to achieve is to attach an error handler function to my object, so that whenever there is an internal error, it is cought, and I don't have to wrap every function call in a try catch block.
I tried object.onerrorbut the error went on to window object. Maybe I am getting the concept wrong. I tried searching on Github for some simpler framework that includes structured error handling, but no luck. I am pretty familiar with this in PHP, but I haven't done this so far in Javascript. Can somebody show me an example how it is done right?
EDIT: I know that structured error handling goes further, I am just trying to get a root handler, so that no errors / exceptions can pass on to the window object
Dealing with the error event without a try catch block will halt the execution of your script (except for any asynchronous functions that have already been called).
You can suppress (non-ajax, non-syntax) errors by capturing them on document.body or a more specific object, and stop them being thrown to the user (or reaching the window object) by using e.preventDefault() or return false, and send them to a global/object handler (to inspect or log) by passing the event object as an argument - but any of those options will stop your script execution beyond the point of error. That's the main benefit of a try catch block, and as far as I know there is no way around that.

Why does exception within frame get no notification in qUnit?

I noticed that qUnit doesn't give any notice when an exception happens in a later part of the test. For example, running this in a test():
stop();
function myfun(ed) {
console.log('resumed');
start(); //Resume qunit
ok(1,'entered qunit again');
ok(ed.getContent()== 'expected content') // < causes exception, no getContent() yet.
}
R.tinymce.onAddEditor.add(myfun)
in an inner iframe on the page will cause an exception (TypeError: ed.getContent is not a function),
but nothing in Qunit status area tells this. I see 0 failures.
(R being the inner iframe, using technique here: http://www.mattevanoff.com/2011/01/unit-testing-jquery-w-qunit/) Would I be correct in assuming this isn't the best way to go for testing sequences of UI interaction that cause certain results? Is it always better to use something like selenium, even for some mostly-javascript oriented frontend web-app tests?
As a side note, the Firefox console shows the console.log below the exception here, even though it happened first... why?
If you look into qUnit source code, there are two mechanisms handling exceptions. One is controlled by config.notrycatch setting and will wrap test setup, execution and teardown in try..catch blocks. This approach won't help much with exceptions thrown by asynchronous tests however, qUnit isn't the caller there. This is why there is an additional window.onerror handler controlled by Test.ignoreGlobalErrors setting. Both settings are false by default so that both kinds of exceptions are caught. In fact, the following code (essentially same as yours but without TinyMCE-specific parts) produces the expected results for me:
test("foo", function()
{
stop();
function myfun(ed)
{
start();
ok(1, 'entered qunit again');
throw "bar";
}
setTimeout(myfun, 1000);
});
I first see a passed tests with the message "entered qunit again" and then a failed one with the message: "uncaught exception: bar." As to why this doesn't work for you, I can see the following options:
Your qUnit copy is more than two years old, before qUnit issue 134 was fixed and a global exception handler added.
Your code is changing Test.ignoreGlobalErrors setting (unlikely).
There is an existing window.onerror handler that returns true and thus tells qUnit that the error has been handled. I checked whether TinyMCE adds one by default but it doesn't look like it does.
TinyMCE catches errors in event handlers when calling them. This is the logical thing to do when dealing with multiple callbacks, the usual approach is something like this:
for (var i = 0; i < callbacks.length; i++)
{
try
{
callbacks[i]();
}
catch (e)
{
console.error(e);
}
}
By redirecting all exceptions to console.error this makes sure that exceptions are still reported while all callbacks will be called even if one of them throws an exception. However, since the exception is handled jQuery can no longer catch it. Again, I checked whether TinyMCE implements this pattern - it doesn't look like it.
Update: Turns out there is a fifth option that I didn't think of: the exception is fired inside a frame and qUnit didn't set up its global error handler there (already because tracking frame creation is non-trivial, a new frame can be created any time). This should be easily fixed by adding the following code to the frame:
window.onerror = function()
{
if (parent.onerror)
{
// Forward the call to the parent frame
return parent.onerror.apply(parent, arguments);
}
else
return false;
}
Concerning your side-note: the console object doesn't guarantee you any specific order in which messages appear. In fact, the code console.log("foo");throw "bar"; also shows the exception first, followed by the log message. This indicates that log messages are queued and handled delayed, probably for performance reasons. But you would need to look into the implementation of the console object in Firefox to be certain - this is an implementation detail.

When developing a library, should we throw errors/exceptions?

I'm developing a pub/sub Mediator library to be used by others and I don't know how to handle errors.
Here's an example piece of code:
/**
*
* #param String channel Channel to subscribe
* #param String|function callback Function name or callback
* #param String context Context to bind function to
* #param Boolean once True to subscribe once
**/
this.subscribe = function (channel, callback, context, once) {
if (!_.isObject(context)) {
context = window;
}
if (!_.isFunction(subscription) && !_.isFunction(context[subscription])) {
throw new Error('Function passed as callback to channel subscription named ' + channel + ' seems invalid!');
}
// Everything ok, add to channels object
channels[channel].push({fn: subscription, context: context || this, once: once});
}
This method is part of the library. It's API expects a channel name and a valid callback (either the function name, a lambda or a callback).
If the channel argument is not a string, the library readily stop. If an invalid context is passed, window is assumed. Mistakes in both these parameters are easily debugaable.
However, if an invalid callback is passed, it propagates the error until an event is fired (a channel publishing is made). In a pub/sub system this might become a nightmare to debug if, for instance, the event is rarely fired.
So my question is...
In this specific scenario, taking in account that I'm developing a javascript library to others, should I:
throw Errors to prevent further error propagation?
return false/undefined/-1 and not register the subscription?
proceed as normal at let it die somewhere, leaving the debugging to the third party developer
NOTE:
There's a similar question but my situation is a bit different and the answers provided didn't put my spirit at ease.
I guess this answer is more of my opinion and what I follow. You have to ask yourself if you should return throw an error and let the consumer, of your library, handle the error. If the error is something that cannot be recovered from, there is no need for you to throw an error (in JS). Your library should be able to fallback gracefully to alternatives but if it cannot do that for a certain function, it should return undefined. Most JS developers do check for undefined and it's a pretty common practice to do so while using library functions. Returning false is usually done in event handlers if an event cannot be handled.
Option #3 should be avoided. At least, return false;. You even might register the invalid callback, expecting that no events are published as well (since something went wrong). This "silent fail if not everything went wrong" might not really be applicable to your pub/sub example, yet there might be use cases for it.
Option #2 sounds good. It somehow makes the callback parameter explicitly optional, which could be a feature of your library - if the user is not sure himself whether he has a reason to subscribe, he can omit that test if you are doing it anyway. You might combine this with a console.warn() message in debugging versions.
Option #1 should be chosen if something really unexpected happened. It allows you to fail with a very descriptive custom Error message. This could be the case if the callback is a truthy object, but not callable, or the channel argument is a boolean or something - depending on how closed your interface design is / how much overloading you provide (in your case you could accept strings to be evaled as callbacks, or arrays of channel strings for example).
Errors and Exceptions are supposed exactly to stop the execution and notify the caller about it.
"throw Errors to prevent further error propagation?" is exactly write choice if your code is not supposed to be executed in that case.
Still, if you expect something to be passed wrong, you can return some special value and describe it in the documentation. Default values are widely used by developers, that's not intuitive,but mostly works tho:
/*
* ...
* Returns a Socket if found, otherwise undefined
*/
function FindSocketOrUndefined(): Socket|undefined {
...
return undefined
}

Categories

Resources