reactjs - 如何在 React 單元測試中對多層自訂元件觸發事件
我正在為我單元測試的功能編寫測試案例。我想編輯一個嵌入在幾個自訂組件中的文字欄位,但我無法弄清楚如何訪問它:
TestFile.test.tsx
it('should be able to change quantity', () =>{
//Mock out dependent function with jest
//Nothing here right now
//Render with the props you want
render(
<MainComponent /> );
//Locate screen components for test
const counter = screen.queryByTestId("counter");
//Perform user actions
fireEvent.change(counter, 4); <--This is the line where I'm trying to access what I want
//Measure against expect cases
expect(counter).toBe(4);
});
MainComponent.tsx
<Counter
data-testid='counter' <--data-testid is here right now
containerMargin={{ marginTop: -5 }}
/>
Counter.tsx
<TextField
{...props}
/>
TextField.tsx
<TextInput
{...props}
/>
錯誤訊息
● Test › should be able to change quantity
Unable to fire a "change" event - please provide a DOM element.
189 |
190 | //Perform user actions
> 191 | fireEvent.change(counter, 4);
| ^
192 |
193 | //Measure against expect cases
194 | expect(counter).toBe(4);
我無法把 data-testid 屬性放在 TextField 或 TextInput 本身,因為在我測試的頁面渲染中有另一個 Counter。任何建議或故障排除的方法都會很有幫助,謝謝!
答案#1
getByTestId 查詢是一個逃生出口。
秉持指導原則的精神,建議僅在其他查詢方法不適用於您的使用案例時使用此方法。使用 data-testid 屬性與您的軟體使用方式不相符,應盡可能避免。
您的文字欄位應該是可存取的。而且應該利用可存取性來存取該元素。
const input = screen.getByRole('textbox', {name: t('priceMultiple')})
// or
const input = screen.getByLabelText(t('priceMultiple'))
直接使用 fireEvent 也是一種逃生出口。
大多數專案對於
fireEvent只有少數幾個使用情境,但大部分時間你可能應該使用@testing-library/user-event。
此建議在 user-event 文件以及這篇部落格文章中有更進一步的說明。
await userEvent.type(input, '4')
請注意,建議在大多數情況下設置一個用戶事件實例,並描述用戶在組件上執行的精確工作流程。因此,大多數測試應該看起來像這樣。
const user = userEvent.setup()
render(<MyComponent/>)
await user.tripleClick(screen.getByRole('textbox', {name: /some label/}))
await user.keyboard('4')
// ... assert that the component output is correct ...
await user.tab()
await user.keyboard('567') // edit another field
// ... perform more assertions ...
答案#2
一般來說,你是對的,除非你將這個屬性傳遞到底層的 DOM 元素,否則在 React 元件上添加 data-testid 是沒用的。幸運的是,<TextField /> 接受 inputProps,它會將其傳遞給 <input /> 元素。
例如
export default function BasicTextFields() {
return (
<ChildComponent data-testid="counter" />
);
}
const ChildComponent = (props) => {
return (
<TextField
inputProps={{ "data-testid": props["data-testid"] }}
id="outlined-basic"
label="Outlined"
variant="outlined"
/>
);
};
然後你可以在測試中使用 getByTestId。
test("should be able to trigger input change", () => {
render(<BasicTextFields />);
const input = screen.getByTestId("counter");
fireEvent.change(input, { target: { value: "23" } });
expect(input.value).toBe("23");
});
https://codesandbox.io/s/flamboyant-joliot-0f49v?file=/src/App.js
我並沒有像你那樣創建深層次的層級來使我的回答更清晰,但只要你將屬性傳遞給 <TextField /> 就可以了。你也可以使用 Context,但這超出了這個回答的範圍。
