Unit Tests on IonInput with React Testing Library

The gold standard in unit testing React apps is React Testing Library (using the DOM Testing Library queries).

So, if you had code like:

<IonLabel>First Name</IonLabel>
<IonInput
  data-testid="firstNameField"
  id="firstName"
  name="firstName"
  onIonChange={setFirstName}
  placeholder="Enter your first name"
  value={firstName}
></IonInput>

you would test it like:

test('it updates an input field', () => {
  const { getByLabelText, getByPlaceholderText } = render(<App />);
  let input = getByLabelText(/first name/i);
  // OR
  input = getByPlaceholderText(/enter your first name/i);
  fireEvent.change(input, { target: { value: 'Jane Doe' } });
});

Unfortunately, this does not work when testing React Ionic apps. It seems there is some lack of ability due to shadow dom with web components.

This is how the DOM is rendered:

<ion-item>
  <ion-label>
    First Name
  </ion-label>
  <ion-input
    id="firstName"
  />
</ion-item>

As you can see, the ion-input is not a real input field; so, my queries can’t target it.

Apparently, there is some disagreement in the Stencil community about what is a unit test and what is an end-to-end test. It looks like the Ionic team leans toward clicking and entering data leans towards the end-to-end test side.

Are there any solutions to this problem? Is it possible to do this type of unit test with React Testing Library in Ionic Framework?

There is a slight update to this. After upgrading to RC2, Ionic does provide more information in the actual HTML output. It now looks like:

<ion-item>
  <ion-label>
    First Name
  </ion-label>
  <ion-input
    data-testid="firstNameField"
    id="firstName"
    name="firstName"
    placeholder="Enter your first name"
    value=""
  />
</ion-item>

However, this does not help with testing. React Testing Library is looking for real input that contains the placeholder text. So, the library still cannot be used for unit testing inputs.

Also unable to test :frowning:

I’ve encountered the same issue also except I don’t see the data-testid in the HTML output using @ionic/react ^4.11.0.

The component:

// exported for testing
export interface SignInProps {
    onEmailAddressChange: (emailAddress: string) => void;
    onPasswordChange: (password: string) => void;
    onSubmit: (emailAddress: string, password: string) => void;
}

const SignInForm: React.FC<SignInProps> = (props) => {

   const [emailAddress, setEmailAddress] = React.useState("");
   const [password, setPassword] = React.useState("");

   ...
   ...
   ...

    return (
        <form data-testid="signin-form" onSubmit={event => handleSubmit(event)} >
            <IonList>
                <IonCard>
                    <IonItem>
                        <IonInput
                            data-testid="emailAddress"
                            placeholder="Email Address"
                            value={emailAddress}
                            type="email"
                            clearInput
                            onIonChange={e => handleEmailChange(e)}></IonInput>
                    </IonItem>
                    <IonItem>
                        <IonInput
                            data-testid="password"
                            placeholder="Password"
                            value={password}
                            type="password"
                            clearInput
                            onIonChange={e => handlePasswordChange(e)}></IonInput>
                    </IonItem>
                </IonCard>
            </IonList>
            <IonButton  size="large" expand="full" shape="round" type="submit">Log in</IonButton>
        </form >
    );

The test:

describe("<SignInForm />", () => {
 test("should allow entering an email address", async () => {
        const onEmailAddressChange = jest.fn();
        const { findByTestId } = renderLoginForm({ onEmailAddressChange });
        const emailAddress = await findByTestId("emailAddress");

        fireEvent.change(emailAddress, { target: { value: "test@test.com" } });

        expect(onEmailAddressChange).toHaveBeenCalledWith("test@test.com");
    });
});

The test error:

<SignInForm /> › should allow entering an email address

    The given element does not have a value setter

      22 |         const emailAddress = await findByTestId("emailAddress");
      23 | 
    > 24 |         fireEvent.change(emailAddress, { target: { value: "test@test.com" } });
         |                   ^
      25 | 
      26 |         expect(onEmailAddressChange).toHaveBeenCalledWith("test@test.com");
      27 |     });

      at setNativeValue (node_modules/@testing-library/dom/dist/events.js:706:13)
      at Object.createEvent.(anonymous function) [as change] (node_modules/@testing-library/dom/dist/events.js:647:7)
      at Function.fireEvent.(anonymous function) [as change] (node_modules/@testing-library/dom/dist/events.js:668:68)
      at node_modules/@testing-library/react/dist/pure.js:149:40
      at batchedUpdates$1 (node_modules/react-dom/cjs/react-dom.development.js:24353:12)
      at act (node_modules/react-dom/cjs/react-dom-test-utils.development.js:1090:14)
      at Function.change (node_modules/@testing-library/react/dist/pure.js:148:28)
      at Object.test (src/pages/SignInContainer/SignInForm/SignInForm.test.tsx:24:19)