網站搜尋

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,但這超出了這個回答的範圍。