reactjsjestjsreact-testing-librarymsw

Test Mocking with SWR - waitForElementToBeRemoved timed out


I am trying to setup some tests for my project, and I'm having some trouble with dealing with asynchronous components which rely on useSWR.

My test file looks like this:

import {
  render,
  screen,
  waitForElementToBeRemoved,
  act,
} from "@testing-library/react";
import { setupServer } from "msw/node";
import { rest } from "msw";
import { AssignmentPanel } from "../components/AssignmentPanel";
import { SWRConfig } from "swr";
import "@testing-library/jest-dom";

const server = setupServer(
  rest.get(
    "http://localhost:8000/:user/:classID/assignments",
    (req, res, ctx) => {
      return res(
        ctx.status(200),
        ctx.json([
          {
            students: "605",
            id: "605",
            firstname: "Chef",
            lastname: "Lastie",
            email: "jias",
            assignments: "asj",
            admins: "null",
          },
          {
            students: null,
            id: null,
            firstname: null,
            lastname: null,
            email: null,
            assignments: "Recess",
            admins: null,
          },
        ])
      );
    }
  )
);

const fetcher = (...args) => {
  fetch(args).then((response) => response.json());
};

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

describe("AssignmentPanel", () => {
  it("displays a list of assignments", async () => {
    render(
      <SWRConfig value={{ fetcher }}>
        <AssignmentPanel></AssignmentPanel>
      </SWRConfig>
    );
    await waitForElementToBeRemoved(() => screen.queryByText("Loading..."));
    const assignmentBox = screen.getByText("Loading...");
    expect(assignmentBox).toBeInTheDocument();
  });
});

When I run the test, I am getting the error "Timed out in waitForElementToBeRemoved." The intended behaviour is that the test will load the component, which will display "Loading..." as it waits for the SWR data variable to be populated, and wait for the data variable to replace "Loading...". Unfortunately this replacement never happens.

The component being tested looks like this:

import React from "react";
import useSWR from "swr";
import { useParams } from "react-router-dom";
import { AssignmentMarksTable } from "./AssignmentMarksTable";
import { CreateNewAssignmentForm } from "./CreateNewAssignmentForm";
import AddIcon from "@mui/icons-material/Add";
import Modal from "./Modal";

export const AssignmentPanel = (): JSX.Element => {
  type Params = {
    user: string;
    classID: string;
  };
  const { user, classID } = useParams<keyof Params>() as Params;
  const {
    data: classAssignmentData,
    error: classAssignmentError,
    mutate,
    isValidating,
  } = useSWR(`http://localhost:8000/${user}/${classID}/assignments`);

  const [selectedAssignment, setSelectedAssignment] = React.useState<string>();

  const [assignmentModalOpen, setAssignmentModalOpen] =
    React.useState<boolean>(false);

  if (classAssignmentData) {
    console.log(classAssignmentData);
  }

  // to do: add a message for when no assignments are found
  return (
    <div className="assignmentPanel">
      <Modal
        open={assignmentModalOpen}
        closeModal={() => {
          setAssignmentModalOpen(false);
        }}
        children={
          <CreateNewAssignmentForm
            modalController={setAssignmentModalOpen}
            mutate={mutate}
          ></CreateNewAssignmentForm>
        }
      ></Modal>
      <div className="selectorColumn" data-testid="assignmentColumn">
        <div
          className="assignmentOrStudentSelectorBox"
          onClick={() => setAssignmentModalOpen(true)}
        >
          <AddIcon></AddIcon>
        </div>
        {classAssignmentData && !isValidating ? (
          classAssignmentData.classInfo.map((item: any) => {
            if (item.assignments) {
              return (
                <div
                  className="assignmentOrStudentSelectorBox"
                  key={item.assignments}
                  data-assignmentname={item.assignments}
                  onClick={(event) => {
                    const result = (event.target as HTMLDivElement).dataset
                      .assignmentname;
                    setSelectedAssignment(result);
                  }}
                >
                  {item.assignments}
                </div>
              );
            }
          })
        ) : (
          <h1>
            {classAssignmentError
              ? "There was an error retrieving your assignments."
              : "Loading..."}
          </h1>
        )}
      </div>
      {selectedAssignment ? (
        <div className="markUpdatePanel">
          <h2>{selectedAssignment}</h2>
          <AssignmentMarksTable
            selectedAssignment={selectedAssignment}
          ></AssignmentMarksTable>
        </div>
      ) : null}
    </div>
  );
};

Any help here would be appreciated.


Solution

  • Your fetcher returns undefined instead of Promise. Please try:

    const fetcher = (...args) => fetch(...args).then((res) => res.json())
    

    It will start to pass the waitForElementToBeRemoved but your test is most probably not finished yet?

    I mean you are waiting for element Loading... to be removed and then you try to get it with getByText which will cause the test to fail.

    await waitForElementToBeRemoved(() => screen.queryByText("Loading..."));
    const assignmentBox = screen.getByText("Loading..."); // Loading element should not be there anymore