Skip to content

Commit 01b1275

Browse files
committed
fix: Use custom reporter to flip expected failures
To verify that the new `fail` matcher works correctly, we need a way to check if the tests failed as expected or not. Typically one would use `test.failing` for this purpose, but that only works if the test threw an exception (e.g. JestException). The `fail` matcher does not do this so that it can still work inside `catch` blocks. Instead, we have to hook into the test reporting and mutate the test results for our expected test failures prior to usage by other test reporters. This commit creates a custom test reporter which detects tests run in the file `fail.test.js` (yes the name is hard-coded) and flips the results of tests inside a `.fail` describe block (i.e. our expected failures). - If the tests failed (expected) we flip the result to a pass. - If the tests passed (unexpected) we flip the result to a fail. The custom reporter also handles the logic for updating the counts for failing test suites so that later reporters reflect the actual test results correctly.
1 parent d0474ee commit 01b1275

File tree

3 files changed

+90
-4
lines changed

3 files changed

+90
-4
lines changed

package.json

+4
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@
103103
"watchPlugins": [
104104
"jest-watch-typeahead/filename",
105105
"jest-watch-typeahead/testname"
106+
],
107+
"reporters": [
108+
"<rootDir>/test/reporters/ExceptionlessExpectedFailureReporter.js",
109+
"default"
106110
]
107111
},
108112
"babel": {

test/matchers/fail.test.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import * as matcher from 'src/matchers/fail';
22

33
expect.extend(matcher);
4-
54
describe('.fail', () => {
6-
xtest('fails without message', () => {
5+
test('fails without message', () => {
76
expect().fail(); // This should fail!
87
});
9-
xtest('fails with message', () => {
8+
test('fails with message', () => {
109
expect().fail('This should fail!');
1110
});
12-
xtest('fails when invoked in a try/catch', () => {
11+
test('fails when invoked in a try/catch', () => {
1312
try {
1413
expect().fail();
1514
} catch (error) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* Flips the test results for fail.test.js > .fail > <test cases>
3+
*/
4+
class ExceptionlessExpectedFailureReporter {
5+
constructor(globalConfig, reporterOptions, reporterContext) {
6+
this._globalConfig = globalConfig;
7+
this._options = reporterOptions;
8+
this._context = reporterContext;
9+
}
10+
onTestCaseResult(test, testCaseResult) {
11+
this._processTestCaseResult(testCaseResult);
12+
}
13+
onTestFileResult(test, testResult, results) {
14+
if (testResult.testFilePath.endsWith('fail.test.js')) {
15+
this._processTestResults(results);
16+
}
17+
}
18+
_processTestResults(results) {
19+
for (let testSuiteResult of results.testResults) {
20+
if (testSuiteResult.testFilePath.endsWith('fail.test.js')) {
21+
let switchedToFailing = 0;
22+
let switchedToPassing = 0;
23+
for (let testCaseResult of testSuiteResult.testResults) {
24+
const processResult = this._processTestCaseResult(testCaseResult);
25+
if (processResult === 'switch-to-failing') switchedToFailing++;
26+
if (processResult === 'switch-to-passing') switchedToPassing++;
27+
}
28+
const originalFailureCount = testSuiteResult.numFailingTests;
29+
testSuiteResult.numFailingTests += switchedToFailing - switchedToPassing;
30+
results.numFailedTests += switchedToFailing - switchedToPassing;
31+
testSuiteResult.numPassingTests += switchedToPassing - switchedToFailing;
32+
results.numPassedTests += switchedToPassing - switchedToFailing;
33+
if (originalFailureCount === switchedToPassing) {
34+
testSuiteResult.failureMessage = '';
35+
results.numFailedTestSuites -= 1;
36+
results.numPassedTestSuites += 1;
37+
if (results.numFailedTestSuites === 0) results.success = true;
38+
console.log('marking failing test suite as passing', testSuiteResult.testFilePath);
39+
}
40+
}
41+
}
42+
}
43+
44+
_processTestCaseResult(testCaseResult) {
45+
if (this._hasDotFailAncestor(testCaseResult)) {
46+
if (testCaseResult.status === 'failed') {
47+
this._markPassing(testCaseResult);
48+
return 'switch-to-passing';
49+
} else if (testCaseResult.status === 'passed') {
50+
this._markFailing(testCaseResult);
51+
return 'switch-to-failing';
52+
}
53+
}
54+
return 'unchanged';
55+
}
56+
_hasDotFailAncestor(result) {
57+
return result.ancestorTitles.length > 0 && result.ancestorTitles[0] === '.fail';
58+
}
59+
_markPassing(result) {
60+
result.status = 'passed';
61+
result.failureDetails = [];
62+
result.failureMessages = [];
63+
result.numPassingAsserts = 1;
64+
}
65+
_markFailing(result) {
66+
const message = `${result.fullName} was expected to fail, but did not.`;
67+
result.status = 'failed';
68+
result.failureDetails = [
69+
{
70+
matcherResult: {
71+
pass: false,
72+
message: message,
73+
},
74+
message: message,
75+
stack: `${message}\n\tNo stack trace.\n\tThis is a placeholder message generated inside ExceptionlessExpectedFailureReporter`,
76+
},
77+
];
78+
result.failureMessages = [message];
79+
result.numPassingAsserts = 0;
80+
}
81+
}
82+
83+
module.exports = ExceptionlessExpectedFailureReporter;

0 commit comments

Comments
 (0)