Search code examples
javascriptreactjsunit-testingjestjsautomated-tests

Unit test failing because fest.fn() not trigger openConfirmationModal function


it('text field', async () => {
 const props = {
   openConfirmationModal: jest.fn()
  }

  const { queryByTestId, findByTestId } = render(
    <FormInputs
      openConfirmationModal={props.openConfirmationModal}
    />
  );

  const input = queryByTestId('input-component-amount') as HTMLInputElement;
  expect(input).toBeInTheDocument();

  fireEvent.change(input, { target: { value: 123 } });

  const textArea = queryByTestId('textarea-component-textfield') as HTMLTextAreaElement;
  expect(textArea).toBeInTheDocument();
  fireEvent.change(textArea, { target: { value: 'This is some text!' } });

  const submitButton = queryByTestId('button') as HTMLButtonElement;
  expect(submitButton).toBeInTheDocument();
  expect(submitButton).toBeEnabled();

  fireEvent.click(submitButton);

  expect(props.openConfirmationModal).toHaveBeenCalledTimes(1);

  const submitModalButton = queryByTestId('confirmation-modal-submit-button') as HTMLButtonElement;
  expect(submitModalButton).toBeInTheDocument();
});
import React, { useState } from 'react';

interface FormInputsProps {
  openConfirmationModal: () => void;
}

const FormInputs: React.FC<FormInputsProps> = ({ openConfirmationModal }) => {
  const [amount, setAmount] = useState<string>('');
  const [reason, setReason] = useState<string>('');
  const [isModalOpen, setIsModalOpen] = useState<boolean>(false);

  const handleSubmit = () => {
    if (amount && reason) {
      openConfirmationModal();
      setIsModalOpen(true);
    }
  };

  const handleModalSubmit = () => {
    Calling API....
    setIsModalOpen(false); // Close modal on confirmation
  };

  return (
    <div>
      <input
        data-testid="input-component-amount"
        type="number"
        value={amount}
        onChange={(e) => setAmount(e.target.value)}
      />
      <textarea
        data-testid="textarea-component-textfield"
        value={reason}
        onChange={(e) => setReason(e.target.value)}
      />
      <button
        data-testid="button"
        onClick={handleSubmit}
        disabled={!amount || !reason}
      >
        Submit
      </button>
      {isModalOpen && (
        <div data-testid="confirmation-modal">
          <p>Are you sure you want to submit?</p>
          <button
            data-testid="confirmation-modal-submit-button"
            onClick={handleModalSubmit}
          >
            Confirm
          </button>
        </div>
      )}
    </div>
  );
};

export default FormInputs;

After expect(props.openConfirmationModal).toHaveBeenCalledTimes(1) unable to see the modal and getting error "unable to find element" for const submitModalButton = queryByTestId('confirmation-modal-submit-button') as HTMLButtonElement.


Solution

  • I see two issues, fireEvent and queryByTestId. Instead of using queryByTestId to find the elements you can directly use findBy or getBy to look for elements. findBy is preferred in case of async usage.

    Reference Doc

    Issue is with the fireEvent, without the act block. I would suggest use userEvent from react testing library inside act block.

    userEvent Docs Please refer to the correct version as latest version have few changes in it.

    await act(async () => {
      await userEvent.type(textArea, "This is some text!");
    }
    await act(async () => {
      await userEvent.click(submitButton);
    });
    

    This is what always preferred. But in anycase if youw ant to continue with fireEvent it is always required to wrap it in act block.

    await act(async () => {
      fireEvent.change(textArea, { target: { value: 'This is some text!' } });
    }
    await act(async () => {
      fireEvent.click(submitButton);
    });
    

    Usually async states do not propogate correctly when using fireEvent, unless it is wrapped in act