How to test checkbox checked with React Testing Library?
Asked Answered
E

2

7

are there anyone see the problem here? If I put checked in state on change it works. But I don't want to duplicate the checked state data and watch it every single props change.

Parent components gets the latest checked information and put it in own state and if parent isChecked change, FormCheckBox isChecked changes. I think, it works async and when I reach latest line of my test code, parent update does not finish, so I see the stale isChecked.

    export default function FormCheckBox(props: IFormCheckBoxProps) {
      const { onChange, label, isChecked, error, errorModelLabel = '' } = props
    
      const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
        onChange(event.currentTarget.checked)
      }
    
      const errorMsg = getFormErrorMsg(error, errorModelLabel)
    
      return (
        <FormGroup
          as={Row}
          className={cx('mb-3', { 'is-invalid': errorMsg })}
          controlId="catalogDescription"
          data-testid="slvy-formcheckbox"
        >
          <FormLabel column className="d-flex align-items-center justify-content-end fw-bold" sm={2}>
            {label}
          </FormLabel>
          <Col className="d-flex align-items-center" sm={10}>
            <input
              checked={isChecked}
              className={cx('checkbox', { 'is-invalid': errorMsg })}
              type="checkbox"
              onChange={handleChange}
            />
            <Form.Control.Feedback type="invalid">{errorMsg}</Form.Control.Feedback>
          </Col>
        </FormGroup>
      )
    }


 it('checkbox must use fireEvent.click', async () => {
    const handleChange = jest.fn()
    props.isChecked = false
    props.onChange = handleChange

    const { container } = render(<FormCheckBox {...props} />)
    const checkbox = container.querySelectorAll("input[type='checkbox']")[0] as HTMLInputElement

    fireEvent.click(checkbox)
    expect(handleChange).toHaveBeenCalledTimes(1)
    
    expect(checkbox.checked).toBe(true)
  })
Excess answered 31/7, 2022 at 14:41 Comment(0)
B
6

Since the FormCheckBox is a controlled component, the input’s value is always driven by the React state. So you need a wrapper component to provide this state. In your test case, even though the mock handleChange function is called, we need to update the isChecked state so that the FormCheckBox component will re-render with this new state.

E.g.

index.tsx:

import React from 'react';

export default function FormCheckBox({ onChange, isChecked }) {

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    onChange(event.currentTarget.checked);
  };

  return <input checked={isChecked} type="checkbox" onChange={handleChange} />;
}

To keep things simple. I removed the irrelevant code.

index.test.tsx:

import { fireEvent, render } from '@testing-library/react';
import React, { useState } from 'react';
import FormCheckBox from './';

describe('73184212', () => {
  test('should pass', () => {
    const Wrap = () => {
      const [isChecked, setIsChecked] = useState(false);
      return <FormCheckBox isChecked={isChecked} onChange={() => setIsChecked(!isChecked)} />;
    };
    const { container } = render(<Wrap />);
    const checkbox = container.querySelectorAll("input[type='checkbox']")[0] as HTMLInputElement;
    fireEvent.click(checkbox);
    expect(checkbox.checked).toBe(true);
  });
});

Test result:

 PASS  stackoverflow/73184212/index.test.tsx (10.998 s)
  73184212
    ✓ should pass (30 ms)

-----------|---------|----------|---------|---------|-------------------
File       | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-----------|---------|----------|---------|---------|-------------------
All files  |     100 |      100 |     100 |     100 |                   
 index.tsx |     100 |      100 |     100 |     100 |                   
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        11.607 s
Bedaub answered 1/8, 2022 at 2:50 Comment(2)
Thank you. In this scenario we dont test how many times handleChange called. For this, I create a function with jest.fn, I created a seperate function for onChange to set isChecked, and inside of that function I set setIsChecked then call mockHandleChange() Is this make sense?Excess
@Excess No, you should let React update the state. Your mock implementation about "updating state" will not re-render the component. Updating the state of React is not just changing the value of the state, the actual implementation is complex. That's why you should use the real useState hook rather than the mock version.Bedaub
F
2

While my response may not directly address the original post, it does provide the answer that I was personally seeking. I was having trouble with the checked state of my checkbox not being registered properly. After doing some digging, I realized that the issue was with my selection method. I was able to successfully solve my issue by using getByRole.

  it("check that checkbox gets checked", () => {
    render(
      <input
        type="checkbox"
        aria-label="checkbox"
        name="checkbox-array"
        value="second"
        id="second"
      />
    );

    const checkbox = screen.getByRole("checkbox", { name: "checkbox" });
    expect(checkbox).not.toBeChecked();
    fireEvent.click(checkbox);
    expect(checkbox).toBeChecked();
  });
Ferrante answered 10/5, 2023 at 8:53 Comment(1)
Upvoted for getting checkbox with Jest functions (getByRole) - that's what I was looking for.Pogey

© 2022 - 2024 — McMap. All rights reserved.