Search code examples
javascriptreactjstypescriptunit-testingjestjs

REACT Typescript JEST test does not find text


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.


Solution

  • 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" }],
      })),
    }));