1

I'm updating an app that was originally built in React 16 and all tests created with enzyme. I have updated to React 17 and am rewriting all tests using React testing library. I'm having trouble figuring out how to test methods on a parent or grandparent component when the method is called in the child/grandchild.

The structure looks like

App => SubmissionForm => Tab1 (it's a multi-tabbed form and each tab is a separate component)

When the user clicks 'submit' on Tab1, it calls a series of methods on SubmissionForm, ending in verifyRecaptchaScore.

verifyRecaptchaScore calls execute from the package react-google-invisible-recaptcha, which calls the callback onResolved. Both the Recaptcha component and the onResolved callback are on the App component.

(App.js)

...

  async onResolved() {
    const token = await this.recaptcha.current.getResponse();
    this.props.apiSubmission.handleInput({
      target: { name: "reCaptchaValue", value: token }
    });
  }


...

<Recaptcha
   ref={refCaptcha}
   sitekey={sitekey}
   onResolved={this.onResolved}
/>

I'm trying to write tests for the methods on the App component. In the old app structure, using enzyme, I was able to test the onResolved method by just calling it programmatically in the test, using wrapper.instance().onResolved()

some relevant excerpts of the old test below:

...

const unconnectedSetup = async () => {
  const setupProps = { ...defaultProps };
  return shallow(<AppUnconnected {...setupProps} />);
};

...

describe("<App />", () => {
  ...

  describe("Misc methods", () => {
    it("onResolved calls recaptcha.getResponse and saves recaptcha token to redux store", async () => {
      wrapper = await unconnectedSetup();
      getResponseMock = jest
        .fn()
        .mockImplementation(() => Promise.resolve("token"));
      wrapper.instance().recaptcha = {
        getResponse: getResponseMock
      };
      await wrapper.update();
      await wrapper.instance().onResolved();
      expect(getResponseMock.mock.calls.length).toBe(1);
      await getResponseMock().then(() => {
        expect(handleInputMock).toHaveBeenCalledWith({
          target: { name: "reCaptchaValue", value: "token" }
        });
      });
    });
    ...
  });

With React testing library, I understand that I'm supposed to try to mimic the user experience rather than call methods programmatically. Which would mean something like:

  • Load App
  • User clicks Next button to load Tab1
  • User clicks Submit button on Tab1 which triggers the chain of methods that eventually calls onResolved

but I can't get this to work. I'm able to simulate the user click that loads Tab1 but when I try to simulate a user click on the Submit button on that tab I can't get it to call any of the other methods, and I'm struggling to even figure out where or how it's failing.

I have tried both userEvent.click(submitButton); and fireEvent.submit(tab1Form);

but neither one seems to work.

Code that's not working:

describe("Misc methods", () => {
    it("onResolved calls recaptcha.getResponse and saves recaptcha token to redux store", async () => {
      getResponseMock = jest
        .fn()
        .mockImplementation(() => Promise.resolve("token"));

      const props = {
        recaptcha: {
          current: {
            getResponseMock
          }
        },
        formValues: {
          ...generateSubmissionBody
        }
      };

      // simulate user click 'Next'
      const user = userEvent.setup();
      const { getByTestId, getByRole } = await setup({ ...props });
      const nextButton = getByTestId("button-next");
      await userEvent.click(nextButton);

      // check that tab 1 renders after clicking 'Next'
      const tab1Form = getByRole("form");
      await waitFor(() => {
        expect(tab1Form).toBeInTheDocument();
      });

      // simulate submit with default formValues
      const submitButton = getByTestId("button-submit");

      await waitFor(() => {
        userEvent.click(submitButton);
        // have also tried fireEvent.submit(tab1Form);
      });

      // expect the mock to have been called once
      await waitFor(() => {
        expect(getResponseMock).toHaveBeenCalled();
      });

      // the above test fails; mock is not called.

      // restore mock
      getResponseMock.mockRestore();
    });

Is this even the right approach for testing this method? If the method is on App but is triggered from Tab1 does anybody have any better suggestions for how to test this? Or how to test a recaptcha callback in general?

If this is the right approach, any suggestions for how to troubleshoot why none of the intermediate methods (or onResolved) are being called when I simulate the user click on Submit? When I try this:

 const handleSubmit = jest.fn();
 tab1Form.onsubmit = handleSubmit;

 // simulate submit with default formValues
 await waitFor(() => {
   fireEvent.submit(tab1Form);
   expect(handleSubmit).toHaveBeenCalled();
 });

the test passes, so I know that fireEvent.submit() is simulating the submission; i just can't get it to work with the real methods that it's supposed to call onSubmit.

0