Embark on a journey through the intricacies of React testing with Emily's latest TWIL installment, where she tackles the elusive Debugging Network Errors in Tests. Discover how to effectively mock API calls and decipher those vexing network errors that often surface during automated test runs. Her insights offer a way to transform temporal bugs from intermittent CI failures into reproducible local issues, ensuring smoother development and more reliable software.
Debugging Network Errors in Tests
A key piece to testing in React is mocking out our API calls.
As part of our testing philosophy, we mock at the lowest level possible (e.g. mock an API response that loads data into the store and/or component instead of mocking a piece of data/props/state or the store itself).
Occasionally, we miss one of these mocks and it results in an error that looks something like this:
FAIL app/containers/ClientPage/tests/ClientPage.test.js
- ClientPage › renders and matches snapshot
Network Error
at createError (node_modules/axios/lib/core/createError.js:16:15)
at XMLHttpRequest.handleError (node_modules/axios/lib/adapters/xhr.js:69:14)
at XMLHttpRequest.<anonymous> (node_modules/jsdom/lib/jsdom/living/helpers/create-event-accessor.js:33:32)
at innerInvokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:316:27)
at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:267:3)
at XMLHttpRequestEventTargetImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:214:9)
at fireAnEvent (node_modules/jsdom/lib/jsdom/living/helpers/events.js:17:36)
at requestErrorSteps (node_modules/jsdom/lib/jsdom/living/xhr-utils.js:121:3)
at Object.dispatchError (node_modules/jsdom/lib/jsdom/living/xhr-utils.js:51:3)
at Request.<anonymous> (node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:675:20)
at Request.onRequestError (node_modules/request/request.js:877:8)
The trick here is that the Network Error
is likely NOT coming from the ClientPage
test. The network call was probably made in a previous test and just happened to time out while this test was running.
This type of bug also seems to pop up more frequently on CI or while updating snapshots locally (probably because these run a bit slower than when running the plain tests locally and the requests don't even have a chance to time out). This also means that this is a temporal bug - sometimes CI will pass and sometimes it won't!
Replicating the Error Locally
The easiest way that I've found to replicate this locally is to bump the timeout in the Axios instance down to something that will cause all API calls to time out almost immediately.
Your project probably has something like this, bump down that timeout:
// Default config options
const DEFAULT_OPTIONS = {
baseURL: process.env.API_BASE_URL,
headers: {
'Content-Type': 'application/json',
},
timeout: 0.1, // Usually set to 15000, in ms
}
// Create instance
const instance = axios.create(DEFAULT_OPTIONS)
Bam! Now you can tell which test the error is actually coming from and see ALL errors deterministically:
Summary of all failing tests
FAIL app/containers/AgentPage/tests/AgentPage.test.js
● AgentPage › does not log errors in console
timeout of 0.1ms exceeded
at createError (node_modules/axios/lib/core/createError.js:16:15)
at XMLHttpRequest.handleTimeout (node_modules/axios/lib/adapters/xhr.js:77:14)
at XMLHttpRequest.<anonymous> (node_modules/jsdom/lib/jsdom/living/helpers/create-event-accessor.js:33:32)
at innerInvokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:316:27)
at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:267:3)
at XMLHttpRequestEventTargetImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:214:9)
at fireAnEvent (node_modules/jsdom/lib/jsdom/living/helpers/events.js:17:36)
at Timeout.properties.timeoutFn [as _onTimeout] (node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:729:15)
FAIL app/containers/ClientPage/tests/ClientPage.test.js
● ClientPage › does not log errors in console
timeout of 0.1ms exceeded
at createError (node_modules/axios/lib/core/createError.js:16:15)
at XMLHttpRequest.handleTimeout (node_modules/axios/lib/adapters/xhr.js:77:14)
at XMLHttpRequest.<anonymous> (node_modules/jsdom/lib/jsdom/living/helpers/create-event-accessor.js:33:32)
at innerInvokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:316:27)
at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:267:3)
at XMLHttpRequestEventTargetImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:214:9)
at fireAnEvent (node_modules/jsdom/lib/jsdom/living/helpers/events.js:17:36)
at Timeout.properties.timeoutFn [as _onTimeout] (node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:729:15)
FAIL app/components/ProfileSidebar/ProfileSidebar.test.js
● ProfileSidebar › does not log errors in console
timeout of 0.1ms exceeded
at createError (node_modules/axios/lib/core/createError.js:16:15)
at XMLHttpRequest.handleTimeout (node_modules/axios/lib/adapters/xhr.js:77:14)
at XMLHttpRequest.<anonymous> (node_modules/jsdom/lib/jsdom/living/helpers/create-event-accessor.js:33:32)
at innerInvokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:316:27)
at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:267:3)
at XMLHttpRequestEventTargetImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:214:9)
at fireAnEvent (node_modules/jsdom/lib/jsdom/living/helpers/events.js:17:36)
at Timeout.properties.timeoutFn [as _onTimeout] (node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:729:15)
Now go mock that service!
- React