Skip to content

Commit e949d57

Browse files
authored
Remove global mocks by adding support for "suppressReactErrorLogging" property (#11636)
* Remove global mocks They are making it harder to test compiled bundles. One of them (FeatureFlags) is not used. It is mocked in some specific test files (and that's fine). The other (FiberErrorLogger) is mocked to silence its output. I'll look if there's some other way to achieve this. * Add error.suppressReactErrorLogging and use it in tests This adds an escape hatch to *not* log errors that go through React to the console. We will enable it for our own tests.
1 parent f114bad commit e949d57

File tree

6 files changed

+53
-73
lines changed

6 files changed

+53
-73
lines changed

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@
119119
".*": "./scripts/jest/preprocessor.js"
120120
},
121121
"setupFiles": [
122-
"./scripts/jest/setup.js",
123122
"./scripts/jest/environment.js"
124123
],
125124
"setupTestFrameworkScriptFile": "./scripts/jest/test-framework-setup.js",

packages/react-reconciler/src/ReactFiberErrorLogger.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export function logCapturedError(capturedError: CapturedError): void {
2525
}
2626

2727
const error = (capturedError.error: any);
28+
const suppressLogging = error && error.suppressReactErrorLogging;
29+
if (suppressLogging) {
30+
return;
31+
}
32+
2833
if (__DEV__) {
2934
const {
3035
componentName,

packages/react-reconciler/src/ReactFiberScheduler.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1004,7 +1004,10 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
10041004
} catch (e) {
10051005
// Prevent cycle if logCapturedError() throws.
10061006
// A cycle may still occur if logCapturedError renders a component that throws.
1007-
console.error(e);
1007+
const suppressLogging = e && e.suppressReactErrorLogging;
1008+
if (!suppressLogging) {
1009+
console.error(e);
1010+
}
10081011
}
10091012

10101013
// If we're in the commit phase, defer scheduling an update on the

packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.js

Lines changed: 35 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ describe('ReactIncrementalErrorHandling', () => {
2121
ReactNoop = require('react-noop-renderer');
2222
});
2323

24+
afterEach(() => {
25+
jest.unmock('../ReactFiberErrorLogger');
26+
});
27+
2428
function div(...children) {
2529
children = children.map(c => (typeof c === 'string' ? {text: c} : c));
2630
return {type: 'div', children, prop: undefined};
@@ -952,49 +956,22 @@ describe('ReactIncrementalErrorHandling', () => {
952956
});
953957

954958
describe('ReactFiberErrorLogger', () => {
955-
function initReactFiberErrorLoggerMock(mock) {
956-
jest.resetModules();
957-
if (mock) {
958-
jest.mock('../ReactFiberErrorLogger');
959-
} else {
960-
jest.unmock('../ReactFiberErrorLogger');
961-
}
962-
React = require('react');
963-
ReactNoop = require('react-noop-renderer');
964-
}
965-
966-
beforeEach(() => {
967-
// Assert that we're mocking this file by default.
968-
// If this ever changes, we'll need to update this test suite anyway.
969-
expect(
970-
require('../ReactFiberErrorLogger').logCapturedError._isMockFunction,
971-
).toBe(true);
972-
});
973-
974-
afterEach(() => {
975-
// Restore the default (we verified it was being mocked in beforeEach).
976-
jest.mock('../ReactFiberErrorLogger');
977-
});
978-
979959
function normalizeCodeLocInfo(str) {
980960
return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
981961
}
982962

983963
it('should log errors that occur during the begin phase', () => {
984-
initReactFiberErrorLoggerMock(false);
985964
// Intentionally spy in production too.
986-
// TODO: It is confusing how these tests correctly "see" console.error()s
987-
// from exceptions becase they called initReactFiberErrorLoggerMock(false)
988-
// and thus unmocked the error logger. It is globally mocked in all other
989-
// tests. We did this to silence warnings from intentionally caught errors
990-
// in tests. But perhaps we should instead make a pass through all tests,
991-
// intentionally assert console.error() is always called for uncaught
992-
// exceptions, and then remove the global mock of ReactFiberErrorLogger.
993965
spyOn(console, 'error');
994966

995967
class ErrorThrowingComponent extends React.Component {
996968
componentWillMount() {
997-
throw Error('componentWillMount error');
969+
const error = new Error('componentWillMount error');
970+
// Note: it's `true` on the Error prototype our test environment.
971+
// That lets us avoid asserting on warnings for each expected error.
972+
// Here we intentionally shadow it to test logging, like in real apps.
973+
error.suppressReactErrorLogging = undefined;
974+
throw error;
998975
}
999976
render() {
1000977
return <div />;
@@ -1030,19 +1007,17 @@ describe('ReactIncrementalErrorHandling', () => {
10301007
});
10311008

10321009
it('should log errors that occur during the commit phase', () => {
1033-
initReactFiberErrorLoggerMock(false);
1034-
// TODO: It is confusing how these tests correctly "see" console.error()s
1035-
// from exceptions becase they called initReactFiberErrorLoggerMock(false)
1036-
// and thus unmocked the error logger. It is globally mocked in all other
1037-
// tests. We did this to silence warnings from intentionally caught errors
1038-
// in tests. But perhaps we should instead make a pass through all tests,
1039-
// intentionally assert console.error() is always called for uncaught
1040-
// exceptions, and then remove the global mock of ReactFiberErrorLogger.
1010+
// Intentionally spy in production too.
10411011
spyOn(console, 'error');
10421012

10431013
class ErrorThrowingComponent extends React.Component {
10441014
componentDidMount() {
1045-
throw Error('componentDidMount error');
1015+
const error = new Error('componentDidMount error');
1016+
// Note: it's `true` on the Error prototype our test environment.
1017+
// That lets us avoid asserting on warnings for each expected error.
1018+
// Here we intentionally shadow it to test logging, like in real apps.
1019+
error.suppressReactErrorLogging = undefined;
1020+
throw error;
10461021
}
10471022
render() {
10481023
return <div />;
@@ -1078,18 +1053,16 @@ describe('ReactIncrementalErrorHandling', () => {
10781053
});
10791054

10801055
it('should ignore errors thrown in log method to prevent cycle', () => {
1081-
initReactFiberErrorLoggerMock(true);
1056+
jest.resetModules();
1057+
jest.mock('../ReactFiberErrorLogger');
1058+
React = require('react');
1059+
ReactNoop = require('react-noop-renderer');
10821060
// Intentionally spy in production too.
1083-
// Note: we're seeing console.error() even though ReactFiberErrorLogger is
1084-
// mocked because we're currently testing the console.error() call inside
1085-
// ReactFiberScheduler (for cycles). It doesn't get affected by mocking.
1086-
// TODO: mocking is confusing and we should simplify this and remove
1087-
// special cases.
10881061
spyOn(console, 'error');
10891062

10901063
class ErrorThrowingComponent extends React.Component {
10911064
render() {
1092-
throw Error('render error');
1065+
throw new Error('render error');
10931066
}
10941067
}
10951068

@@ -1099,7 +1072,12 @@ describe('ReactIncrementalErrorHandling', () => {
10991072
ReactFiberErrorLogger.logCapturedError.mockImplementation(
11001073
capturedError => {
11011074
logCapturedErrorCalls.push(capturedError);
1102-
throw Error('logCapturedError error');
1075+
const error = new Error('logCapturedError error');
1076+
// Note: it's `true` on the Error prototype our test environment.
1077+
// That lets us avoid asserting on warnings for each expected error.
1078+
// Here we intentionally shadow it to test logging, like in real apps.
1079+
error.suppressReactErrorLogging = undefined;
1080+
throw error;
11031081
},
11041082
);
11051083

@@ -1124,14 +1102,7 @@ describe('ReactIncrementalErrorHandling', () => {
11241102
});
11251103

11261104
it('should relay info about error boundary and retry attempts if applicable', () => {
1127-
initReactFiberErrorLoggerMock(false);
1128-
// TODO: It is confusing how these tests correctly "see" console.error()s
1129-
// from exceptions becase they called initReactFiberErrorLoggerMock(false)
1130-
// and thus unmocked the error logger. It is globally mocked in all other
1131-
// tests. We did this to silence warnings from intentionally caught errors
1132-
// in tests. But perhaps we should instead make a pass through all tests,
1133-
// intentionally assert console.error() is always called for uncaught
1134-
// exceptions, and then remove the global mock of ReactFiberErrorLogger.
1105+
// Intentionally spy in production too.
11351106
spyOn(console, 'error');
11361107

11371108
class ParentComponent extends React.Component {
@@ -1155,7 +1126,12 @@ describe('ReactIncrementalErrorHandling', () => {
11551126

11561127
class ErrorThrowingComponent extends React.Component {
11571128
componentDidMount() {
1158-
throw Error('componentDidMount error');
1129+
const error = new Error('componentDidMount error');
1130+
// Note: it's `true` on the Error prototype our test environment.
1131+
// That lets us avoid asserting on warnings for each expected error.
1132+
// Here we intentionally shadow it to test logging, like in real apps.
1133+
error.suppressReactErrorLogging = undefined;
1134+
throw error;
11591135
}
11601136
render() {
11611137
renderAttempts++;

scripts/jest/environment.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,12 @@ global.requestIdleCallback = function(callback) {
2323
global.cancelIdleCallback = function(callbackID) {
2424
clearTimeout(callbackID);
2525
};
26+
27+
// By default React console.error()'s any errors, caught or uncaught.
28+
// However it is annoying to assert that a warning fired each time
29+
// we assert that there is an exception in our tests. This lets us
30+
// opt out of extra console error reporting for most tests except
31+
// for the few that specifically test the logging by shadowing this
32+
// property. In real apps, it would usually not be defined at all.
33+
Error.prototype.suppressReactErrorLogging = true;
34+
DOMException.prototype.suppressReactErrorLogging = true;

scripts/jest/setup.js

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)