@samitouri / QOS-React / commits / e949d57508

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');