Search code examples
reactjsunit-testingreact-reduxjestjsreact-testing-library

How to do unit testing of Modal in react using jest


// Delete.jsx

import React, {useCallback, useRef} from "react"
import {useDispatch} from "react-redux"
import {ModalComponent, modalOpen} from "../Modal"

export function Delete () {
    const dispatch = useDispatch()
    const modalRef = useRef(null)
    const handleDelete = () => {
        dispatchEvent(deleteData())
        modalRef.current.close
    }
    return (
        <Type>
            <Button
                onClick={modalOpen(modalRef)}
            >
                Delete
            </Button>
            <ModalComponent func={handleDelete} refer={modalRef} />
        </Type>
    )
}

// ModalComponent.jsx

import React, {useCallback} from "react"

export function ModalComponent (props) {
    const modalRef = props.refer

    return (
        <Modal
            ref={modalRef}
            onClose={modalClose(modalRef)}
            >
                <Type>
                    Are you sure ?
                </Type>
                <Type>
                    <ButtonGroup>
                        <Button
                            id='DeleteButton'
                            onClick={modalClose(modalRef)}
                        >
                            Close
                        </Button>
                        <Button
                            onClick={props.func}
                        >
                            Confirm
                        </Button>
                    </ButtonGroup>
                </Type>
            </Modal>
    )
}

export const modalOpen = (refModal) => {
    return useCallback(
        () => {
            refModal.current.open()
        }, [refModal]
    )
}

export const modalClose = (refModal) => {
    return useCallback(
        () => {
            refModal.current.close()
        }, [refModal]
    )
}

Above are the codes of Delete and ModalComponent, I'm trying to write unit test cases for Delete Button.

// Delete.test.js

import React from "react"
import {fireEvent, render} from "@testing-library/react"
import * as modal from '../ModalComponent'

jest.mock('react-redux', () => ({
    ...jest.requireActual('react-redux'),
    useDispatch: jest.fn()
}))

describe('Delete button', () => {
    it('should open modal when delete button is called', () => {
        const modalSpy = jest.spyOn(modal, 'modalOpen').mockImplementation(() => jest.fn())
        const component = render(
            <Delete />
        )
        const button = component.container.querySelector('#DeleteButton')
        fireEvent.click(button)
        expect(modalSpy).toHaveBeenCalled()
    })
})

Here what I tried to test if modal is opening or not and it's working but I want to test Delete funcationality but here I've mocked the modal so I'm not sure how to test the handle delete and when I tried without mocking the modal it throws error.

Could somebody please help me to handle modal events so I can test handle edit ?


Solution

  • Issues

    Don't mock what you are trying to unit test. Your unit tests should mirror how users or other components interact with the component, e.g. via its rendered UI and props.

    Suggestion

    Since you can't share the Modal component I've created a mock modal component that exposes out open and close handlers via a React ref.

    Mock Modal component:

    import React, {
      forwardRef,
      useImperativeHandle,
      useState
    } from "react";
    
    const Modal = forwardRef((props, ref) => {
      const [open, setOpen] = useState(false);
    
      useImperativeHandle(
        ref,
        () => ({
          open: () => setOpen(true),
          close: () => setOpen(false)
        }),
        []
      );
    
      return open ? (
        <div data-testid="modal">
          <button type="button" onClick={props.onClose}>
            x
          </button>
          <div>{props.children}</div>
        </div>
      ) : null;
    });
    

    Delete

    const modalOpen = (refModal) => () => {
      refModal.current.open();
    };
    
    const modalClose = (refModal) => () => {
      refModal.current.close();
    };
    
    export default function Delete() {
      // const dispatch = useDispatch();
      const modalRef = useRef(null);
    
      const handleDelete = () => {
        // dispatchEvent(deleteData())
        modalRef.current.close();
      };
    
      return (
        <Type>
          <Button onClick={modalOpen(modalRef)}>Delete</Button>
          <ModalComponent func={handleDelete} refer={modalRef} />
        </Type>
      );
    }
    

    Delete unit test

    import React from "react";
    import { fireEvent, render } from "@testing-library/react";
    import "@testing-library/jest-dom";
    import Delete from "./Delete";
    
    describe("Delete button", () => {
      it("should open modal when delete button is called", () => {
        const { queryByText, queryByTestId } = render(<Delete />);
    
        // Assert that the modal is not current in the DOM
        expect(queryByTestId("modal")).not.toBeInTheDocument();
    
        // Click delete button to toggle the modal open
        fireEvent.click(queryByText("Delete"));
    
        // Assert that the modal is now in the DOM
        expect(queryByTestId("modal")).toBeInTheDocument();
    
        // Click modal's close button to toggle the modal closed
        fireEvent.click(queryByText("Close"));
    
        // Assert that the modal is not current in the DOM
        expect(queryByTestId("modal")).not.toBeInTheDocument();
      });
    });
    

    enter image description here

    When you update your code and uncomment the useDispatch hook and dispatch call you'll need to also create a Redux store for the test.

    Basic example:

    import React from "react";
    import { fireEvent, render } from "@testing-library/react";
    import "@testing-library/jest-dom";
    import { configureStore } from "@reduxjs/toolkit";
    import { Provider } from "react-redux";
    import reducer from "../path/to/rootReducer";
    import Delete from "./Delete";
    
    const store = configureStore({
      reducer,
    });
    
    const Providers = ({ children }) => (
      <Provider store={store}>
        {children}
      </Provider>
    );
    
    describe("Delete button", () => {
      it("should open modal when delete button is called", () => {
        const { .... } = render(<Delete />, { wrapper: Providers });
    
        ... testing code ...
      });
    });
    

    See the Redux Writing Tests documentation for further general testing information and strategies.