web-dev-qa-db-ja.com

react-testing-libraryでreact-selectをテストする方法

App.js

import React, { Component } from "react";
import Select from "react-select";

const SELECT_OPTIONS = ["FOO", "BAR"].map(e => {
  return { value: e, label: e };
});

class App extends Component {
  state = {
    selected: SELECT_OPTIONS[0].value
  };

  handleSelectChange = e => {
    this.setState({ selected: e.value });
  };

  render() {
    const { selected } = this.state;
    const value = { value: selected, label: selected };
    return (
      <div className="App">
        <div data-testid="select">
          <Select
            multi={false}
            value={value}
            options={SELECT_OPTIONS}
            onChange={this.handleSelectChange}
          />
        </div>
        <p data-testid="select-output">{selected}</p>
      </div>
    );
  }
}

export default App;

App.test.js

import React from "react";
import {
  render,
  fireEvent,
  cleanup,
  waitForElement,
  getByText
} from "react-testing-library";
import App from "./App";

afterEach(cleanup);

const setup = () => {
  const utils = render(<App />);
  const selectOutput = utils.getByTestId("select-output");
  const selectInput = document.getElementById("react-select-2-input");
  return { selectOutput, selectInput };
};

test("it can change selected item", async () => {
  const { selectOutput, selectInput } = setup();
  getByText(selectOutput, "FOO");
  fireEvent.change(selectInput, { target: { value: "BAR" } });
  await waitForElement(() => getByText(selectOutput, "BAR"));
});

この最小限の例はブラウザーで期待どおりに機能しますが、テストは失敗します。のonChangeハンドラが呼び出されていないと思います。テストでonChangeコールバックをトリガーするにはどうすればよいですか? fireEventの要素を見つけるための推奨される方法は何ですか?ありがとうございました

19
user2133814

これは、RTLについて最もよく聞かれる質問です:D

最良の戦略は jest.mock (またはテストフレームワークの同等のもの)を使用して、選択をモックし、代わりにHTML選択をレンダリングします。

これが最善のアプローチである理由の詳細については、このケースにも当てはまるものを書きました。 OPはMaterial-UIでの選択について質問しましたが、考え方は同じです。

元の質問 と私の答え:

そのUIを制御できないためです。サードパーティのモジュールで定義されています。

したがって、2つのオプションがあります。

マテリアルライブラリが作成するHTMLを把握し、container.querySelectorを使用してその要素を見つけて操作することができます。しばらく時間がかかりますが、可能になるはずです。これらをすべて実行した後は、新しいリリースごとにDOM構造があまり変更されないこと、またはすべてのテストを更新する必要があることを期待する必要があります。

もう1つのオプションは、Material-UIが機能するコンポーネントを作成し、ユーザーが使用できることを信頼することです。その信頼に基づいて、テストでそのコンポーネントを単純に置き換えて、より単純なコンポーネントにすることができます。

はい、オプション1はユーザーに表示されるものをテストしますが、オプション2は保守が簡単です。

私の経験では、2番目のオプションで十分ですが、もちろん、ユースケースが異なる場合があり、実際のコンポーネントをテストする必要がある場合があります。

これは、selectをモックする方法の例です。

jest.mock("react-select", () => ({ options, value, onChange }) => {
  function handleChange(event) {
    const option = options.find(
      option => option.value === event.currentTarget.value
    );
    onChange(option);
  }
  return (
    <select data-testid="select" value={value} onChange={handleChange}>
      {options.map(({ label, value }) => (
        <option key={value} value={value}>
          {label}
        </option>
      ))}
    </select>
  );
});

あなたはもっと読むことができます ここ

14

私のプロジェクトでは、react-testing-libraryとjest-domを使用しています。私は同じ問題に遭遇しました-いくつかの調査の後、スレッドに基づいて解決策を見つけました: https://github.com/airbnb/enzyme/issues/4

Renderのトップレベルの関数は、個々のステップと同様に非同期でなければならないことに注意してください。

この場合、フォーカスイベントを使用する必要はなく、複数の値を選択できます。

また、getSelectItem内に非同期コールバックが必要です。

const DOWN_ARROW = { keyCode: 40 };

it('renders and values can be filled then submitted', async () => {
  const {
    asFragment,
    getByLabelText,
    getByText,
  } = render(<MyComponent />);

  ( ... )

  // the function
  const getSelectItem = (getByLabelText, getByText) => async (selectLabel, itemText) => {
    fireEvent.keyDown(getByLabelText(selectLabel), DOWN_ARROW);
    await waitForElement(() => getByText(itemText));
    fireEvent.click(getByText(itemText));
  }

  // usage
  const selectItem = getSelectItem(getByLabelText, getByText);

  await selectItem('Label', 'Option');

  ( ... )

}
15
momimomo

@momimomoの回答と同様に、TypeScriptのreact-selectからオプションを選択する小さなヘルパーを作成しました。

ヘルパーファイル:

import { getByText, fireEvent, waitForElement } from '@testing-library/react';

const keyDownEvent = {
    key: 'ArrowDown',
};

export async function selectOption(container: HTMLElement, optionText: string) {
    const placeholder = getByText(container, 'Select...');
    fireEvent.keyDown(placeholder, keyDownEvent);
    await waitForElement(() => getByText(container, optionText));
    fireEvent.click(getByText(container, optionText));
}

使用法:

export const MyComponent: React.FunctionComponent = () => {
    return (
        <div data-testid="day-selector">
            <Select {...reactSelectOptions} />
        </div>
    );
};
it('can select an option', async () => {
    const { getByTestId } = render(<MyComponent />);
    // Open the react-select options then click on "Monday".
    await selectOption(getByTestId('day-selector'), 'Monday');
});
3
Vestride
export async function selectOption(container: HTMLElement, optionText: string) {
  let listControl: any = '';
  await waitForElement(
    () => (listControl = container.querySelector('.Select-control')),
  );
  fireEvent.mouseDown(listControl);
  await wait();
  const option = getByText(container, optionText);
  fireEvent.mouseDown(option);
  await wait();
}

注:コンテナー:選択ボックスのコンテナー(例:コンテナー= getByTestId( 'seclectTestId'))

0
tushar kumar