antd Select 테스트 코드 작성하기
- Authors
- 조원영
문제 상황
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);
});
원인
제대로 작동하지 않은 이유는 내부 구조가 다르기 때문입니다. 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("옵션 선택");
해결 방법
테스트 케이스 실행 전, antd 라이브러리를 모킹하여 Select로 구현된 코드를 원하는 구조로 변환하는 방법을 사용했습니다.
- 변환시켜줄 코드 형태
<select placeholder="옵션 선택">
<option value="option1">
옵션1
</option>
<option value="option2">
옵션2
</option>
</select>
- 변환하는 코드
- antd 라이브러리를 실제로 가져와서 복사본을 생성
- Select 컴포넌트 정의
- 새로운
<select>
엘리먼트를 생성하고 기존 antd Select 속성을 사용 - 여기서 props.children은 Select.Option
- SelectOption 컴포넌트 정의
- 새로운
<option>
엘리먼트를 생성하고 기존 antd Select.Option 속성을 사용 - Select.Option을 SelectOption으로 설정
- 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());