I have a very small REACT Typescript application setup with VITE. I have installed all of the REACT testing packages as needed. I have one very simple component that makes a API call to JSONPlaceholder and renders a list of posts. I have created a custom hook in which I use fetch to make the API call. I wrote a JEST test to validate this component and the fetch hook. I have managed to get past all of the fetch is undefined errors. When I run my JEST test, I am looking for some text to be on the page, text that is provided by my mocked fetch in the JEST code. However, when the test runs it fails to find the text I have in my mocked fetch.
Here is my useFetch hook:
import { useEffect, useState } from "react";
function useFetch<T>(url: string) {
const [apiData, setApiData] = useState<T>();
const [pending, setPending] = useState(false);
const [serverError, setServerError] = useState(false);
useEffect(() => {
setPending(true);
fetch(url, {
method: "GET",
})
.then((res) => {
if (!res.ok) {
throw Error("Unable to fetch data");
}
return res.json();
})
.then((data: T) => {
setApiData(data);
setPending(false);
})
.catch((err) => {
setPending(false);
setServerError(err.message);
});
}, [url]);
return { apiData, pending, serverError };
}
export default useFetch;
Here is my component:
import useFetch from "./useFetch";
import { IPosts } from "./IPosts";
export default function APIDemo() {
const { apiData, pending, serverError } = useFetch<IPosts[]>(
"https://jsonplaceholder.typicode.com/posts"
);
return (
<div>
<h1>POSTS</h1>
{pending && <p>Loading...</p>}
{serverError && <p>{serverError}</p>}
<>
{apiData?.map((post: IPosts) => (
<div key={post.id}>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
))}
</>
</div>
);
}
Here is my Test for the component:
import { act, render, screen, waitFor } from "@testing-library/react";
import APIDemo from "./APIDemo";
describe("API Demo", () => {
beforeEach(() => {
global.fetch = jest.fn().mockImplementation(() =>
Promise.resolve({
status: 200,
json: () =>
Promise.resolve([
{ id: 1, title: "Something1", body: "body1" },
{ id: 2, title: "Something2", body: "body2" },
]),
})
);
});
it("Should render heading POSTS", async () => {
render(<APIDemo />);
await waitFor(() => {
screen.getByText("POSTS");
});
});
it("Should render mocked post", async () => {
await act(async () => render(<APIDemo />));
expect(screen.getByText("Something")).toBeInTheDocument();
});
});
When I run the test, here is the result:
FAIL src/APIDemo.test.tsx
API Demo
√ Should render heading POSTS (44 ms)
× Should render mocked post (10 ms)
API Demo › Should render mocked post
TestingLibraryElementError: Unable to find an element with the text: Something. This
could be because the text is broken up by multiple elements. In this case, you can
provide a function for your text matcher to make your matcher more flexible.
Ignored nodes: comments, script, style
<body>
<div>
<div>
<h1>
POSTS
</h1>
<p>
Unable to fetch data
</p>
</div>
</div>
</body>
56 | it("Should render mocked post", async () => {
57 | await act(async () => render(<APIDemo />));
> 58 | expect(screen.getByText("Something")).toBeInTheDocument();
| ^
59 | });
60 | });
61 |
at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:37:19)
at node_modules/@testing-library/dom/dist/query-helpers.js:76:38
at node_modules/@testing-library/dom/dist/query-helpers.js:52:17
at node_modules/@testing-library/dom/dist/query-helpers.js:95:19
at Object.<anonymous> (src/APIDemo.test.tsx:58:19)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 1 passed, 2 total
Snapshots: 0 total
Time: 3.505 s
Ran all test suites.
I have looked many posts on Stackoverflow and watched several YouTube videos on how to test FETCH with JEST and none of the solutions have worked for me thus far! Hoping someone else can see or find my issue here.
There's a couple of problems here. First and foremost, you're missing properties on the mocked response object that your useFetch
hook relies on, namely ok
.
const jsonFn = jest.fn(async () => [
{ id: 1, title: "Something", body: "body" },
]);
global.fetch = jest.fn(async () => ({
ok: true,
json: jsonFn,
}));
Note: I've set the json()
function as a separate mock so you can verify it
expect(jsonFn).toHaveBeenCalled();
But you're also testing two systems at once here; both your component and the useFetch
hook. You should ideally have separate tests for each and mock useFetch
in your component tests
jest.mock("./useFetch", () => ({
__esModule: true,
default: jest.fn(() => ({
pending: false,
serverError: "",
apiData: [{ id: 1, title: "Something", body: "body" }],
})),
}));