How to Fix UnhandledPromiseRejectionWarning: TypeError (nodeType of null) in React 16 Tests with react-testing-library & axios-hooks
Testing React components that rely on asynchronous data fetching can be tricky, especially when using libraries like axios-hooks for data fetching and react-testing-library for testing. A common roadblock developers encounter is the UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'nodeType' of null error. This error typically arises when asynchronous operations (like API calls) in your component aren’t properly handled in tests, leading to unmounted components or unresolved promises.
In this blog, we’ll demystify this error, explore its root causes, and walk through step-by-step solutions to fix it. By the end, you’ll be able to write robust tests for components using axios-hooks without encountering this frustrating warning.
Table of Contents#
- Understanding the Error
- Root Causes
- Step-by-Step Fixes
- Common Pitfalls to Avoid
- Conclusion
- References
Understanding the Error#
Let’s break down the error message:
UnhandledPromiseRejectionWarning: This occurs when a Promise is rejected but not caught by a.catch()ortry/catchblock. In testing, this often means an API call (viaaxios-hooks) failed, and the test didn’t handle the rejection.TypeError: Cannot read property 'nodeType' of null: React throws this when it tries to update the DOM for a component that has already been unmounted. This happens if an asynchronous operation (e.g., an API response) resolves after the component is unmounted (e.g., when the test finishes prematurely).
Example Scenario#
Imagine a component that uses axios-hooks to fetch user data:
// UserProfile.js
import { useAxios } from 'axios-hooks';
export const UserProfile = ({ userId }) => {
const [{ data, loading, error }] = useAxios(`/api/users/${userId}`);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>Hello, {data.name}!</div>;
};A test for this component might fail with the error above if:
- The test doesn’t wait for the API call to resolve.
axiosisn’t mocked, leading to real (unhandled) network requests.- The component unmounts before the
axios-hooksPromise resolves.
Root Causes#
The error stems from one or more of these issues in your test setup:
- Unmocked Axios: If
axiosisn’t mocked,axios-hookswill make real network requests, leading to unhandled rejections (since real APIs may not respond in tests). - Synchronous Test Execution: Using synchronous queries (e.g.,
getByText) instead of asynchronous ones (e.g.,findByText) causes the test to finish before the API call resolves. - Premature Component Unmounting: The test ends before the
axios-hooksPromise resolves, unmounting the component. The hook then tries to update state on an unmounted component, leading to thenodeTypeerror. - Invalid Mock Responses: Mocked Axios responses are incomplete (e.g., missing
dataproperty), causingaxios-hooksto reject the Promise unexpectedly.
Step-by-Step Fixes#
Let’s resolve the error with actionable steps. We’ll use the UserProfile component above and fix its test.
1. Mock Axios Properly#
axios-hooks relies on axios under the hood. Always mock axios to avoid real network requests and ensure controlled responses.
How to Mock Axios:#
Use jest.mock to replace axios with a mock implementation. Define resolved/rejected responses for axios.get (or other methods) to simulate API success/failure.
// UserProfile.test.js
import { render, screen } from '@testing-library/react';
import { UserProfile } from './UserProfile';
import axios from 'axios';
// Mock axios entirely
jest.mock('axios');
test('renders user name on successful fetch', async () => {
// Mock a successful response
axios.get.mockResolvedValue({
data: { id: '1', name: 'John Doe' }, // Match the shape expected by the component
});
render(<UserProfile userId="1" />);
// Assert loading state first (optional but helpful)
expect(screen.getByText('Loading...')).toBeInTheDocument();
// Wait for the async operation to resolve and check the result
const userGreeting = await screen.findByText('Hello, John Doe!');
expect(userGreeting).toBeInTheDocument();
});2. Use Async/Await with React Testing Library Queries#
React Testing Library provides asynchronous queries to handle components that load data asynchronously:
findBy*queries: Async and wait for elements to appear (e.g.,findByText,findByRole). These wrapwaitForandgetBy*under the hood.waitFor: Explicitly waits for an assertion to pass (useful for retries).
Never use synchronous getBy* queries for async content—they’ll fail if the element isn’t immediately present.
Example with waitFor:#
test('renders error message on API failure', async () => {
// Mock a rejected response
axios.get.mockRejectedValue(new Error('Network Error'));
render(<UserProfile userId="1" />);
// Wait for the error message to appear
const errorMessage = await screen.findByText('Error: Network Error');
expect(errorMessage).toBeInTheDocument();
});3. Handle Component Unmounting and Cleanup#
Tests often unmount components automatically when they finish. If an axios-hooks Promise resolves after unmounting, React will throw the nodeType error.
Fix: Ensure All Async Operations Resolve Before the Test Ends#
- Await all async queries: Use
awaitwithfindBy*orwaitForto ensure the test doesn’t finish prematurely. - Avoid unmounting manually (e.g., with
unmountfromrender) unless necessary. If you must unmount, resolve all pending Promises first.
Example: Forcing Cleanup#
If your component uses useEffect with cleanup, ensure it cancels pending requests. However, this is rarely needed if tests are written to await async operations.
4. Validate Mocked Responses#
axios-hooks expects responses to match axios’s format (e.g., a data property in the response). Invalid mocks can cause unhandled rejections.
Common Mocking Mistakes:#
-
Forgetting the
dataproperty:// ❌ Invalid: axios returns { data: ... }, not the raw object axios.get.mockResolvedValue({ id: '1', name: 'John Doe' }); // ✅ Valid: wraps data in the axios response format axios.get.mockResolvedValue({ data: { id: '1', name: 'John Doe' } }); -
Mocking the wrong HTTP method (e.g.,
axios.postinstead ofaxios.get).
Common Pitfalls to Avoid#
- Forgetting to Mock Axios: Always mock
axioswithjest.mock('axios')to prevent real network requests. - Using
getBy*for Async Content:getBy*is synchronous and will fail if the element isn’t immediately present. UsefindBy*orwaitForinstead. - Not Awaiting Async Operations: Tests with async logic must be marked
async, and async queries must beawaited. - Cross-Test Contamination: Reset mocks between tests with
jest.clearAllMocks()inbeforeEachto avoid leftover mock implementations.
beforeEach(() => {
jest.clearAllMocks(); // Reset axios mocks between tests
});Conclusion#
The UnhandledPromiseRejectionWarning: TypeError (nodeType of null) error in React tests is caused by unhandled async operations and premature component unmounting. To fix it:
- Mock
axiosto control API responses. - Use async queries (
findBy*,waitFor) andawaitthem. - Ensure tests wait for all async operations to resolve before finishing.
- Validate mock responses to match
axios’s format.
By following these steps, you’ll write stable, reliable tests for components using react-testing-library and axios-hooks.