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.
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