New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ensure unhandled rejections cause test failure. #1241
Changes from 6 commits
336b32e
bc23a65
42eebf7
f3e30f4
9a77ca4
6a6fae8
455ac83
4b955d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { test } from "../test"; | ||
|
||
import config from "./config"; | ||
import { extend } from "./utilities"; | ||
import { sourceFromStacktrace } from "./stacktrace"; | ||
|
||
// Handle an unhandled rejection | ||
export default function onUnhandledRejection( reason ) { | ||
const resultInfo = { | ||
result: false, | ||
message: reason.message || "error", | ||
actual: reason, | ||
source: reason.stack || sourceFromStacktrace( 3 ) | ||
}; | ||
|
||
const currentTest = config.current; | ||
if ( currentTest ) { | ||
currentTest.assert.pushResult( resultInfo ); | ||
} else { | ||
test( "global failure", extend( function( assert ) { | ||
assert.pushResult( resultInfo ); | ||
}, { validTest: true } ) ); | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
"use strict"; | ||
|
||
QUnit.module( "Unhandled Rejections", function() { | ||
QUnit.test( "test passes just fine, but has a rejected promise", function( assert ) { | ||
assert.ok( true ); | ||
|
||
const done = assert.async(); | ||
|
||
new Promise( function( resolve ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct me if I'm wrong, but can't this just be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, you are absolutely correct. I have been working too much with Ember's internal Updated to be much more idiomatic... |
||
setTimeout( resolve ); | ||
} ) | ||
.then( function() { | ||
|
||
// throwing a non-Error here because stack trace representation | ||
// across Node versions is not stable (they continue to get better) | ||
throw { | ||
message: "Error thrown in non-returned promise!", | ||
stack: `Error: Error thrown in non-returned promise! | ||
at /some/path/wherever/unhandled-rejection.js:13:11` | ||
}; | ||
} ); | ||
|
||
// prevent test from exiting before unhandled rejection fires | ||
setTimeout( done, 10 ); | ||
} ); | ||
|
||
// rejecting with a non-Error here because stack trace representation | ||
// across Node versions is not stable (they continue to get better) | ||
Promise.reject( { | ||
message: "outside of a test context", | ||
stack: `Error: outside of a test context | ||
at Object.<anonymous> (/some/path/wherever/unhandled-rejection.js:20:18)` | ||
} ); | ||
} ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// Detect if the current browser supports `onunhandledrejection` (avoiding | ||
// errors for browsers without the capability) | ||
var HAS_UNHANDLED_REJECTION_HANDLER = "onunhandledrejection" in window; | ||
|
||
if ( HAS_UNHANDLED_REJECTION_HANDLER ) { | ||
QUnit.module( "Unhandled Rejections inside test context", function( hooks ) { | ||
hooks.beforeEach( function( assert ) { | ||
var originalPushResult = assert.pushResult; | ||
assert.pushResult = function( resultInfo ) { | ||
|
||
// Inverts the result so we can test failing assertions | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Curious to know why this comment is de-indented from the line below. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a mistake, though I'm surprised that the otherwise aggressive indentation linting rules didn't catch it... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No worries. The indent rule is slightly lenient about comments because in general it is hard to tell what lines of code a comment might be associated with, so ESLint generally allows comments to align with the next line of code, the previous line of code, or with the "correct" indentation (i.e., the indentation we would expect a token to actually be at if this were a token instead of a comment). My guess is the indent rule is allowing this location due to aligning with line 9. That seems weird since line 10 is whitespace-only, so I wonder if we could improve the rule to not use the previous-line heuristic if the previous token is actually 2 lines above the comment instead of 1 (and similarly, not use the next-line heuristic if the next token is actually 2 lines below the comment instead of 1). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Totally makes sense! (also cleaned up the indentation) |
||
resultInfo.result = !resultInfo.result; | ||
originalPushResult( resultInfo ); | ||
}; | ||
} ); | ||
|
||
QUnit.test( "test passes just fine, but has a rejected promise", function( assert ) { | ||
const done = assert.async(); | ||
|
||
new Promise( function( resolve ) { | ||
setTimeout( resolve ); | ||
} ) | ||
.then( function() { | ||
throw new Error( "Error thrown in non-returned promise!" ); | ||
} ); | ||
|
||
// prevent test from exiting before unhandled rejection fires | ||
setTimeout( done, 10 ); | ||
} ); | ||
|
||
} ); | ||
|
||
QUnit.module( "Unhandled Rejections outside test context", function( hooks ) { | ||
var originalPushResult; | ||
|
||
hooks.beforeEach( function( assert ) { | ||
|
||
// Duck-punch pushResult so we can check test name and assert args. | ||
originalPushResult = assert.pushResult; | ||
|
||
assert.pushResult = function( resultInfo ) { | ||
|
||
// Restore pushResult for this assert object, to allow following assertions. | ||
this.pushResult = originalPushResult; | ||
|
||
this.strictEqual( this.test.testName, "global failure", "Test is appropriately named" ); | ||
|
||
this.deepEqual( | ||
resultInfo, | ||
{ | ||
message: "Error message", | ||
source: "filePath.js:1", | ||
result: false, | ||
actual: { | ||
message: "Error message", | ||
fileName: "filePath.js", | ||
lineNumber: 1, | ||
stack: "filePath.js:1" | ||
} | ||
}, | ||
"Expected assert.pushResult to be called with correct args" | ||
); | ||
}; | ||
} ); | ||
|
||
hooks.afterEach( function() { | ||
QUnit.config.current.pushResult = originalPushResult; | ||
} ); | ||
|
||
// Actual test (outside QUnit.test context) | ||
QUnit.onUnhandledRejection( { | ||
message: "Error message", | ||
fileName: "filePath.js", | ||
lineNumber: 1, | ||
stack: "filePath.js:1" | ||
} ); | ||
} ); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is
reason
just anError
instance?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No,
reason
is whatever the rejection value is. AnError
instance is almost certainly the most common thing thatreason
would be, but it could also be a failed XHR, a string,undefined
,null
, etc. The value here is either whatever the argument toreject()
or the argument tothrow
is.See small demo JSBin with the following content:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah okay, makes sense. Early-morning me couldn't quite make that connection.