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.
Dan Abramov committed
Nov 23, 2017 at 01:15 UTC
e949d57508dc0248ca12ba4ce13ed62bed008548
6 files changed
+53
-73
package.json
-1
@@ -119,7 +119,6 @@
119
".*": "./scripts/jest/preprocessor.js"
120
},
121
"setupFiles": [
122
- "./scripts/jest/setup.js",
122
"./scripts/jest/environment.js"
123
],
124
"setupTestFrameworkScriptFile": "./scripts/jest/test-framework-setup.js",
packages/react-reconciler/src/ReactFiberErrorLogger.js
+5
@@ -25,6 +25,11 @@ export function logCapturedError(capturedError: CapturedError): void {
25
}
26
27
const error = (capturedError.error: any);
28
+ const suppressLogging = error && error.suppressReactErrorLogging;
29
+ if (suppressLogging) {
30
+ return;
31
+ }
32
+
33
if (__DEV__) {
34
const {
35
componentName,
packages/react-reconciler/src/ReactFiberScheduler.js
+4
-1
@@ -1004,7 +1004,10 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
1004
} catch (e) {
1005
// Prevent cycle if logCapturedError() throws.
1006
// 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
+ }
1011
}
1012
1013
// If we're in the commit phase, defer scheduling an update on the
packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.js
+35
-59
@@ -21,6 +21,10 @@ describe('ReactIncrementalErrorHandling', () => {
21
ReactNoop = require('react-noop-renderer');
22
});
23
24
+ afterEach(() => {
25
+ jest.unmock('../ReactFiberErrorLogger');
26
+ });
27
+
28
function div(...children) {
29
children = children.map(c => (typeof c === 'string' ? {text: c} : c));
30
return {type: 'div', children, prop: undefined};
@@ -952,49 +956,22 @@ describe('ReactIncrementalErrorHandling', () => {
956
});
957
958
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
-
959
function normalizeCodeLocInfo(str) {
960
return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
961
}
962
963
it('should log errors that occur during the begin phase', () => {
984
- initReactFiberErrorLoggerMock(false);
964
// 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.
965
spyOn(console, 'error');
966
967
class ErrorThrowingComponent extends React.Component {
968
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;
975
}
976
render() {
977
return <div />;
@@ -1030,19 +1007,17 @@ describe('ReactIncrementalErrorHandling', () => {
1007
});
1008
1009
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.
1011
spyOn(console, 'error');
1012
1013
class ErrorThrowingComponent extends React.Component {
1014
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;
1021
}
1022
render() {
1023
return <div />;
@@ -1078,18 +1053,16 @@ describe('ReactIncrementalErrorHandling', () => {
1053
});
1054
1055
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');
1060
// 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.
1061
spyOn(console, 'error');
1062
1063
class ErrorThrowingComponent extends React.Component {
1064
render() {
1092
- throw Error('render error');
1065
+ throw new Error('render error');
1066
}
1067
}
1068
@@ -1099,7 +1072,12 @@ describe('ReactIncrementalErrorHandling', () => {
1072
ReactFiberErrorLogger.logCapturedError.mockImplementation(
1073
capturedError => {
1074
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;
1081
},
1082
);
1083
@@ -1124,14 +1102,7 @@ describe('ReactIncrementalErrorHandling', () => {
1102
});
1103
1104
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.
1106
spyOn(console, 'error');
1107
1108
class ParentComponent extends React.Component {
@@ -1155,7 +1126,12 @@ describe('ReactIncrementalErrorHandling', () => {
1126
1127
class ErrorThrowingComponent extends React.Component {
1128
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;
1135
}
1136
render() {
1137
renderAttempts++;
scripts/jest/environment.js
+9
@@ -23,3 +23,12 @@ global.requestIdleCallback = function(callback) {
23
global.cancelIdleCallback = function(callbackID) {
24
clearTimeout(callbackID);
25
};
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
deleted
-12
@@ -1,12 +0,0 @@
1
-'use strict';
2
-
3
-jest.mock('shared/ReactFeatureFlags', () => {
4
- // We can alter flags based on environment here
5
- // (e.g. for CI runs with different flags).
6
- return require.requireActual('shared/ReactFeatureFlags');
7
-});
8
-
9
-// Error logging varies between Fiber and Stack;
10
-// Rather than fork dozens of tests, mock the error-logging file by default.
11
-// TODO: direct imports like some-package/src/* are bad. Fix me.
12
-jest.mock('react-reconciler/src/ReactFiberErrorLogger');