logo

antd Select 테스트 코드 작성하기

Authors
  • avatar
    조원영

문제 상황

UI 라이브러리 antd를 사용한 프로젝트의 BDD 테스트를 수행하면서 Select 테스트 코드를 작성했습니다. react-testing-library userEvent의 selectOptions 통해 옵션을 선택하는 시뮬레이션을 하였으나 작동하지 않았고, 'option2'를 찾지 못했다는 에러가 발생했습니다.

  • antd Select 사용한 컴포넌트
export default function Test() {
  const options = [
    {
      value: "option1",
      label: "옵션1",
    },
    {
      value: "option2",
      label: "옵션2",
    }
  ];

  return (
    <Select placeholder="옵션 선택">
      {options.map((item) => (
        <Select.Option key={item.value} value={item.value}>
          {item.label}
        </Select.Option>
      ))}
    </Select>
  );
}
  • 작성한 테스트코드
test("antd Select test", async () => {
	const user = userEvent.setup();
	render(<Test />);
	const selectBox = await screen.findByRole("combobox");
	await user.selectOptions(selectBox, "option2");
	expect(
    (screen.getByRole("option", { name: "옵션2" }) as HTMLOptionElement).selected,
  ).toBe(true);
});
option2

원인

제대로 작동하지 않은 이유는 내부 구조가 다르기 때문입니다. antd의 Select는 일반적으로 select box를 구현하는 <select>, <option> 태그 대신 <div>, <input>, <span> 태그로 구현되어 있습니다. 따라서 react-testing-library로 테스트 하기 어려운 구조였고, selectOptions가 의도한 대로 동작하지 않았습니다.

  • antd Select 실제 구조
<div class="ant-select css-dev-only-do-not-override-2q8sxy ant-select-single ant-select-show-arrow">
	<div class="ant-select-selector">
		<span class="ant-select-selection-search">
			<input
				role="combobox"
				type="search"
				value=""
				... />
    </span>
    <span class="ant-select-selection-placeholder">
      옵션 선택
    </span>
  </div>
</div>

placeholder도 <span> 태그로 구현되어 있어서 ByPlaceHolderText 쿼리셀렉터로 접근하면 요소를 찾을 수 없다는 에러가 발생합니다.

const selectBox = await screen.findByPlaceholderText("옵션 선택");
placeholder

해결 방법

테스트 케이스 실행 전, antd 라이브러리를 모킹하여 Select로 구현된 코드를 원하는 구조로 변환하는 방법을 사용했습니다.

  • 변환시켜줄 코드 형태
<select placeholder="옵션 선택">
  <option value="option1">
	옵션1
  </option>
  <option value="option2">
    옵션2
  </option>
</select>
  • 변환하는 코드
    1. antd 라이브러리를 실제로 가져와서 복사본을 생성
    2. Select 컴포넌트 정의
    • 새로운 <select> 엘리먼트를 생성하고 기존 antd Select 속성을 사용
    • 여기서 props.children은 Select.Option
    1. SelectOption 컴포넌트 정의
    • 새로운 <option> 엘리먼트를 생성하고 기존 antd Select.Option 속성을 사용
    • Select.Option을 SelectOption으로 설정
    1. antd와 새로운 Select 컴포넌트를 반환
interface OptionProps {
  children: string;
  value: string;
}

export const antdMock = async () => {
  const antd = await vi.importActual("antd");

  const Select = (props: SelectProps) => {
    const handleOnChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
      const selectedValue = e.target.value;

      if (props.onChange) {
        const option: DefaultOptionType = {
          label: selectedValue,
          value: selectedValue,
        };
        props.onChange(selectedValue, option);
      }
    };

    return (
      <select
        className={props.className}
        data-value={props.value || undefined}
        defaultValue={props.defaultValue || undefined}
        id={props.id || undefined}
        onChange={handleOnChange}
        value={props.value || undefined}
        placeholder={(props.placeholder as string) || undefined}
      >
        {props.children}
      </select>
    );
  };

  const SelectOption: React.FC<OptionProps> = ({ children, ...otherProps }) => {
    return <option {...otherProps}>{children}</option>;
  };

  Select.Option = SelectOption;

  return { ...antd, Select };
};

테스트코드 파일에 모킹을 적용시키면 성공적으로 테스트를 통과할 수 있습니다.

vi.mock("antd", () => antdMock());

참고: https://stackoverflow.com/questions/71574735/ant-design-react-testing-library-testing-form-with-select