reactjsreact-testing-librarybabel-jest

testing conditional component in react


I'm trying the unit test for the first time. I would like to test the function of rendering in modal or dialog depending on the condition.

I've seen countless posts and videos, but I haven't seen a case like mine. In the case of my modal, I saw Udemy and implemented a course by Maximilian Schwarzmüller.

This is a YouTube link from one of the videos I referenced. https://youtu.be/To2PzUT1lQ4?si=MYC_E_i_S2jafa-z

This is another Question , but this is Props,i'm not using props conditional component How to test conditional rendering of components using Jest and Enzyme

I think it's the most similar question, but it's hard for me to apply it to my case. testing conditional rendering that relies on state using react testing library

https://medium.com/@manojmukherjee777/react-testing-library-portal-modal-b05aaeb5dda7

the Test Order

  1. rendering FetchRenderTest

  2. click "Testing Start" Button

  3. Running fetchmock

  4. It is assumed that a request or communication to a response fails. so change isError to true

  5. rendering ErrorModal.tsx <- I don't know what to do from this part. (Explain it again - ErrorModal is a modal or dialog that appears on the screen as described above.)

  6. "Error dialog close" button Verifying Rendered

my code here

FetchRenderTest.tsx

import React, { useState, Fragment } from "react";

import ErrorModal from "./UI/ErrorModal";

function FetchRenderTest() {
  const [isError, setIsError] = useState(false);
  const errorHandler = () => {
    setIsError(false);
  };

  const confirmHandler = async () => {
    let domain = window.location.hostname;
    try {
      const response = await fetch("<this is url>", {
        method: "PUT",
        headers: {
          "Content-type": "application/json",
        },
        body: JSON.stringify({ SendData: "test" }),
      });

      if (response.status === 200) {
        return;
      } else {
        setIsError(true);
        return;
      }
    } catch (error) {
      setIsError(true);
    }
  };
  return (
    <>
      {isError && <ErrorModal role="dialog" title="Modal, Dialog Test" message="" onConfirm={errorHandler} />}
      <div>
        <button onClick={confirmHandler}> Testing Start</button>
      </div>
    </>
  );
}

export default FetchRenderTest;


ErrorModal.tsx

import React, { Fragment } from "react";
import ReactDOM from "react-dom";
import PopupDragControl from "./PopupDragControl";
import Button from "./Button";
import classes from "./ErrorModal.module.css";

const ErrorModalOverlay = (props: any) => {
  return (
    <div>
      <div className={classes.backdrop} />
      <PopupDragControl>
        <header className={classes.header}>
          <h2>{props.title}</h2>
        </header>
        <div className={classes.content}>
          <p>{props.message}</p>
        </div>
        <footer className={classes.action}>
          <Button onClick={props.isConfirm}> Error dialog close </Button>
        </footer>
      </PopupDragControl>
    </div>
  );
};

const portalElement = document.getElementById("overlays"); <- I tried the "root" 
const ErrorModal = (props: any) => {
  const { title, message, onConfirm } = props;
  if (!portalElement) {
    return null;
  }
  const messageText = message ? message.result : null;
  const errorModalElements = ReactDOM.createPortal(<ErrorModalOverlay title={title} message={messageText} isConfirm={onConfirm}></ErrorModalOverlay>, portalElement);

  return <Fragment>{errorModalElements}</Fragment>;
};

export default ErrorModal;

Button.tsx

const Button = (props : any) => {
  return (
    <button
      type={props.type || "button"}
      className={`${classes.button} ${props.className}`}
      onClick={props.onClick}
      disabled={props.disabled}
    >
      {props.children}
    </button>
  );
};

export default Button;

And Test code FetchRenderTest.test.tsx

import React from "react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import "@testing-library/jest-dom";

import FetchRenderTest from "./FetchRenderTest";

test("renders learn react link", async () => {
  global.fetch = jest.fn().mockRejectedValueOnce({
    json: jest.fn().mockRejectedValueOnce({ success: true }),
    ok: true,
    status: 500,
  });

  render(<FetchRenderTest />);

  fireEvent.click(screen.getByText(/Testing Start/i, { exact: false }));

  await waitFor(() => {
    expect(global.fetch).toHaveBeenCalledWith("<this is url>", {
      method: "PUT",
      headers: {
        "Content-type": "application/json",
      },
      body: JSON.stringify({ SendData: "test" }),
    });
  });
fireEvent.click(screen.getByText(/Error dialog close/i, { exact: false }));
});

coverlage image

an idea from one of the attempts Default setting for React In addition, modals that are not all renders Rendering to .

I guess the problem is id So I modified it all to root, but it still doesn't test.

And I looked up all the usual methods with Googling, but it didn't fit my case.

The search method may not have been good.

In this case, if you have to test each component separately, please let me know


Solution

  • In your test code, you are on the right track. After clicking the "Error dialog close" button, you need to mock fetch request, render, assert. Here's how you can modify your test to achieve this:

    Full Code with added your code and test file. Please run npm run test https://stackblitz.com/~/github.com/2manoj1/example-model-test-vitest

    If you want to local run: clone below repo https://github.com/2manoj1/example-model-test-vitest

    npm i npm run test or npm run test:ui

    run project npm run dev

    Using [MSW][1]

    // FetchRenderTest.test.js
    
    import {
        render,
        screen,
        userEvent,
        waitFor,
        debug,
        act,
    } from "./utils/test-utils";
    import { http, HttpResponse } from "msw";
    import { setupServer } from "msw/node";
    import FetchRenderTest from "./FetchRenderTest";
    
    const handlers = [
        http.put("https://65d1ac70987977636bfb57a9.mockapi.io/api/v1/test", () => {
            return HttpResponse.json({ msg: "Invalid request" }, { status: 400 }); // Simulate error response
        }),
    ];
    const server = setupServer(...handlers);
    
    beforeAll(() => server.listen());
    afterEach(() => server.resetHandlers());
    afterAll(() => server.close());
    
    test("renders ErrorModal when fetch fails", async () => {
        render(<FetchRenderTest />, { container: document.body });
    
        const testingStartButton = screen.getByText("Testing Start");
        await act(async () => {
            // Click the button to trigger the fetch request
            await userEvent.click(testingStartButton);
        });
    
        // Wait for the ErrorModal to render
        await waitFor(() => {
            const errorModal = screen.getByRole("dialog");
            expect(errorModal).toBeInTheDocument();
        });
    
        // Verify the modal content
        const modalTitle = screen.getByText("Modal, Dialog Test");
        expect(modalTitle).toBeInTheDocument();
    
        // Verify the close button is rendered
        const closeButton = screen.getByRole("button", {
            name: /Error dialog close/i,
        });
        expect(closeButton).toBeInTheDocument();
    });
    
    

    This test:

    1. Sets up MSW to intercept fetch requests and mock a failed request with status code 500.
    2. Renders the FetchRenderTest component.
    3. Clicks the "Testing Start" button to trigger the fetch request.
    4. Waits for the error modal to be rendered and asserts that it contains the expected title and close button.
    5. Clicks the close button on the error modal.
    6. Waits for the error modal to be removed from the document.

    This test ensures that the component behaves correctly when a fetch request fails, displaying the error modal and allowing the user to close it.