Report async expectations that complete after the runable completes
It's very easy to forget to `await` or `return` the promise returned from `expectAsync`. When that happens, the expectation failure will occur after the spec or suite's result has been reported to reporters, and the failure will typically not be shown to the user. This change adds a top-level suite failure in that case, similar to the way we report unhandled exceptions or promise rejections that occur after the runable completes. Adding the error at the top level gives us the best chance of getting in before the set of failures we add it to is sent to reporters. See #1752.
This commit is contained in:
@@ -1233,7 +1233,33 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
}
|
||||
};
|
||||
|
||||
var asyncExpectationFactory = function(actual, spec) {
|
||||
function recordLateExpectation(runable, runableType, result) {
|
||||
var delayedExpectationResult = {};
|
||||
Object.keys(result).forEach(function(k) {
|
||||
delayedExpectationResult[k] = result[k];
|
||||
});
|
||||
delayedExpectationResult.passed = false;
|
||||
delayedExpectationResult.globalErrorType = 'lateExpectation';
|
||||
delayedExpectationResult.message =
|
||||
runableType +
|
||||
' "' +
|
||||
runable.getFullName() +
|
||||
'" ran a "' +
|
||||
result.matcherName +
|
||||
'" expectation after it finished.\n';
|
||||
|
||||
if (result.message) {
|
||||
delayedExpectationResult.message +=
|
||||
'Message: "' + result.message + '"\n';
|
||||
}
|
||||
|
||||
delayedExpectationResult.message +=
|
||||
'Did you forget to return or await the result of expectAsync?';
|
||||
|
||||
topSuite.result.failedExpectations.push(delayedExpectationResult);
|
||||
}
|
||||
|
||||
var asyncExpectationFactory = function(actual, spec, runableType) {
|
||||
return j$.Expectation.asyncFactory({
|
||||
util: j$.matchersUtil,
|
||||
customEqualityTesters: runnableResources[spec.id].customEqualityTesters,
|
||||
@@ -1243,9 +1269,19 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
});
|
||||
|
||||
function addExpectationResult(passed, result) {
|
||||
if (currentRunnable() !== spec) {
|
||||
recordLateExpectation(spec, runableType, result);
|
||||
}
|
||||
return spec.addExpectationResult(passed, result);
|
||||
}
|
||||
};
|
||||
var suiteAsyncExpectationFactory = function(actual, suite) {
|
||||
return asyncExpectationFactory(actual, suite, 'Suite');
|
||||
};
|
||||
|
||||
var specAsyncExpectationFactory = function(actual, suite) {
|
||||
return asyncExpectationFactory(actual, suite, 'Spec');
|
||||
};
|
||||
|
||||
var defaultResourcesForRunnable = function(id, parentRunnableId) {
|
||||
var resources = {
|
||||
@@ -1463,7 +1499,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
id: getNextSuiteId(),
|
||||
description: 'Jasmine__TopLevel__Suite',
|
||||
expectationFactory: expectationFactory,
|
||||
asyncExpectationFactory: asyncExpectationFactory,
|
||||
asyncExpectationFactory: suiteAsyncExpectationFactory,
|
||||
expectationResultFactory: expectationResultFactory
|
||||
});
|
||||
defaultResourcesForRunnable(topSuite.id);
|
||||
@@ -1796,7 +1832,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
description: description,
|
||||
parentSuite: currentDeclarationSuite,
|
||||
expectationFactory: expectationFactory,
|
||||
asyncExpectationFactory: asyncExpectationFactory,
|
||||
asyncExpectationFactory: suiteAsyncExpectationFactory,
|
||||
expectationResultFactory: expectationResultFactory,
|
||||
throwOnExpectationFailure: config.oneFailurePerSpec
|
||||
});
|
||||
@@ -1890,7 +1926,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
id: getNextSpecId(),
|
||||
beforeAndAfterFns: beforeAndAfterFns(suite),
|
||||
expectationFactory: expectationFactory,
|
||||
asyncExpectationFactory: asyncExpectationFactory,
|
||||
asyncExpectationFactory: specAsyncExpectationFactory,
|
||||
resultCallback: specResultCallback,
|
||||
getSpecName: function(spec) {
|
||||
return getSpecName(spec, suite);
|
||||
|
||||
@@ -2149,7 +2149,7 @@ describe("Env integration", function() {
|
||||
env.it('is a spec without any expectations', function() {
|
||||
// does nothing, just a mock spec without expectations
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
it('should report "failed" status if "failSpecWithNoExpectations" is enabled', function(done) {
|
||||
@@ -2556,4 +2556,93 @@ describe("Env integration", function() {
|
||||
|
||||
env.execute();
|
||||
});
|
||||
|
||||
it('reports an error when an async expectation occurs after the spec finishes', function(done) {
|
||||
jasmine.getEnv().requirePromises();
|
||||
|
||||
var env = new jasmineUnderTest.Env(),
|
||||
resolve,
|
||||
promise = new Promise(function(res) { resolve = res; });
|
||||
|
||||
env.describe('a suite', function() {
|
||||
env.it('does not wait', function() {
|
||||
// Note: we intentionally don't return the result of each expectAsync.
|
||||
// This causes the spec to finish before the expectations are evaluated.
|
||||
env.expectAsync(promise).toBeResolved();
|
||||
env.expectAsync(promise).toBeResolvedTo('something else');
|
||||
});
|
||||
});
|
||||
|
||||
env.addReporter({
|
||||
specDone: function() {
|
||||
resolve();
|
||||
},
|
||||
jasmineDone: function (result) {
|
||||
expect(result.failedExpectations).toEqual([
|
||||
jasmine.objectContaining({
|
||||
passed: false,
|
||||
globalErrorType: 'lateExpectation',
|
||||
message: 'Spec "a suite does not wait" ran a "toBeResolved" expectation ' +
|
||||
'after it finished.\n' +
|
||||
'Did you forget to return or await the result of expectAsync?',
|
||||
matcherName: 'toBeResolved'
|
||||
}),
|
||||
jasmine.objectContaining({
|
||||
passed: false,
|
||||
globalErrorType: 'lateExpectation',
|
||||
message: 'Spec "a suite does not wait" ran a "toBeResolvedTo" expectation ' +
|
||||
'after it finished.\n' +
|
||||
'Message: "Expected a promise to be resolved to \'something else\' ' +
|
||||
'but it was resolved to undefined."\n' +
|
||||
'Did you forget to return or await the result of expectAsync?',
|
||||
matcherName: 'toBeResolvedTo'
|
||||
})
|
||||
]);
|
||||
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
env.execute();
|
||||
});
|
||||
|
||||
it('reports an error when an async expectation occurs after the suite finishes', function(done) {
|
||||
jasmine.getEnv().requirePromises();
|
||||
|
||||
var env = new jasmineUnderTest.Env(),
|
||||
resolve,
|
||||
promise = new Promise(function(res) { resolve = res; });
|
||||
|
||||
env.describe('a suite', function() {
|
||||
env.afterAll(function() {
|
||||
// Note: we intentionally don't return the result of expectAsync.
|
||||
// This causes the suite to finish before the expectations are evaluated.
|
||||
env.expectAsync(promise).toBeResolved();
|
||||
});
|
||||
|
||||
env.it('is a spec', function() {});
|
||||
});
|
||||
|
||||
env.addReporter({
|
||||
suiteDone: function() {
|
||||
resolve();
|
||||
},
|
||||
jasmineDone: function (result) {
|
||||
expect(result.failedExpectations).toEqual([
|
||||
jasmine.objectContaining({
|
||||
passed: false,
|
||||
globalErrorType: 'lateExpectation',
|
||||
message: 'Suite "a suite" ran a "toBeResolved" expectation ' +
|
||||
'after it finished.\n' +
|
||||
'Did you forget to return or await the result of expectAsync?',
|
||||
matcherName: 'toBeResolved'
|
||||
})
|
||||
]);
|
||||
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
env.execute();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -321,7 +321,33 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
}
|
||||
};
|
||||
|
||||
var asyncExpectationFactory = function(actual, spec) {
|
||||
function recordLateExpectation(runable, runableType, result) {
|
||||
var delayedExpectationResult = {};
|
||||
Object.keys(result).forEach(function(k) {
|
||||
delayedExpectationResult[k] = result[k];
|
||||
});
|
||||
delayedExpectationResult.passed = false;
|
||||
delayedExpectationResult.globalErrorType = 'lateExpectation';
|
||||
delayedExpectationResult.message =
|
||||
runableType +
|
||||
' "' +
|
||||
runable.getFullName() +
|
||||
'" ran a "' +
|
||||
result.matcherName +
|
||||
'" expectation after it finished.\n';
|
||||
|
||||
if (result.message) {
|
||||
delayedExpectationResult.message +=
|
||||
'Message: "' + result.message + '"\n';
|
||||
}
|
||||
|
||||
delayedExpectationResult.message +=
|
||||
'Did you forget to return or await the result of expectAsync?';
|
||||
|
||||
topSuite.result.failedExpectations.push(delayedExpectationResult);
|
||||
}
|
||||
|
||||
var asyncExpectationFactory = function(actual, spec, runableType) {
|
||||
return j$.Expectation.asyncFactory({
|
||||
util: j$.matchersUtil,
|
||||
customEqualityTesters: runnableResources[spec.id].customEqualityTesters,
|
||||
@@ -331,9 +357,19 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
});
|
||||
|
||||
function addExpectationResult(passed, result) {
|
||||
if (currentRunnable() !== spec) {
|
||||
recordLateExpectation(spec, runableType, result);
|
||||
}
|
||||
return spec.addExpectationResult(passed, result);
|
||||
}
|
||||
};
|
||||
var suiteAsyncExpectationFactory = function(actual, suite) {
|
||||
return asyncExpectationFactory(actual, suite, 'Suite');
|
||||
};
|
||||
|
||||
var specAsyncExpectationFactory = function(actual, suite) {
|
||||
return asyncExpectationFactory(actual, suite, 'Spec');
|
||||
};
|
||||
|
||||
var defaultResourcesForRunnable = function(id, parentRunnableId) {
|
||||
var resources = {
|
||||
@@ -551,7 +587,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
id: getNextSuiteId(),
|
||||
description: 'Jasmine__TopLevel__Suite',
|
||||
expectationFactory: expectationFactory,
|
||||
asyncExpectationFactory: asyncExpectationFactory,
|
||||
asyncExpectationFactory: suiteAsyncExpectationFactory,
|
||||
expectationResultFactory: expectationResultFactory
|
||||
});
|
||||
defaultResourcesForRunnable(topSuite.id);
|
||||
@@ -884,7 +920,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
description: description,
|
||||
parentSuite: currentDeclarationSuite,
|
||||
expectationFactory: expectationFactory,
|
||||
asyncExpectationFactory: asyncExpectationFactory,
|
||||
asyncExpectationFactory: suiteAsyncExpectationFactory,
|
||||
expectationResultFactory: expectationResultFactory,
|
||||
throwOnExpectationFailure: config.oneFailurePerSpec
|
||||
});
|
||||
@@ -978,7 +1014,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
id: getNextSpecId(),
|
||||
beforeAndAfterFns: beforeAndAfterFns(suite),
|
||||
expectationFactory: expectationFactory,
|
||||
asyncExpectationFactory: asyncExpectationFactory,
|
||||
asyncExpectationFactory: specAsyncExpectationFactory,
|
||||
resultCallback: specResultCallback,
|
||||
getSpecName: function(spec) {
|
||||
return getSpecName(spec, suite);
|
||||
|
||||
Reference in New Issue
Block a user