Как получить доступ к внутренним частям компонента react и макету вызова api с помощью фреймворка тестирования jest и enzyme?

#javascript #unit-testing #react-redux #jestjs #enzyme

#javascript #модульное тестирование #react-redux #jestjs #enzyme

Вопрос:

Я создаю набор модульных тестов javascript для компонента react-redux с помощью jest amp; enzyme; рассматриваемый компонент представляет собой форму загрузки файла с несколькими полями, которая имеет некоторые базовые изменения стиля на основе пользовательского ввода и / или ответа сервера. Jest и enzyme настроены и работают, и я могу успешно писать базовые тесты нормально.

Я хочу имитировать взаимодействие пользователя с этими элементами пользовательского интерфейса (файловый ввод и текстовые поля) с помощью jest и enzyme, но у меня возникают трудности с этим; Я довольно новичок в использовании этих платформ тестирования и имею базовое представление о react-redux, и я немного перегружен, убедившись, что правильноечасти созданы для написания значимых модульных тестов.

В общем плане: этот модульный тест проводится после факта, несколько месяцев назад я присоединился к этому проекту, который использует методы разработки, основанные на тестировании, и я раньше так не разрабатывал, поэтому я использую это в качестве примера / шаблона для будущего развития.

На данный момент модульные тесты успешно создают компонент (с энзимом mount amp; shallow с mockStore и начальным состоянием). Я попытался сделать две вещи:

  1. создайте __mocks__ api только для модульного тестирования, которое использует другой модуль (редуктор действия redux, который использует вспомогательный метод, который вызывает api), согласно документам jest здесь
  2. поиск / доступ к полям формы (область ввода файла и текст) для имитации взаимодействия с пользователем

Я не был полностью успешным в обоих случаях и провел модульные тесты, которые охватывают их аспекты, но не полностью интегрированы.

вот метод рендеринга для компонента, для которого я пытаюсь выполнить модульные тесты:

 render() {
    const { dataset } = this.props;

    let serverResp;
    dataset ? serverResp = dataset.fileUploadResp : null;

    let catFeats = this.state.catFeatures;
    let errorMsg = this.state.errorResp;
    let ordFeatureSelection = "";
    let catFeatureSelection = "";
    let dataPrevTable = this.getDataTablePreview();
    // default to hidden until a file is selected, then display input areas
    let formInputClass = "file-upload-form-hide-inputs";

    // server message to display in popup (or other UI element)
    serverResp ? serverResp = ( <p style={{display: 'block'}}> {JSON.stringify(serverResp)} </p> ) :
                 null;
    // check if file with filename has been selected, if so then use css to show form
    this.state.selectedFile amp;amp; this.state.selectedFile.name ?
      formInputClass = "file-upload-form-show-inputs" : null;

    return (
      <div>
        <Form inverted>
          <Segment className="file-upload-segment">
            <Input
              className="file-upload-form-text-input"
              type="file"
              label="Select new dataset"
              id="upload_dataset_file_browser_button"
              onChange={this.handleSelectedFile}
            />
            <br/>
            <div
              id="file-upload-form-input-area"
              className={formInputClass}
            >
              <Form.Input
                label="Dependent Column"
                placeholder="class"
                value={this.state.dependentCol ? this.state.dependentCol : ""}
                type="text"
                onChange={this.handleDepColField}
              />
              <Form.Input
                label="Ordinal Features"
              >
                <textarea
                  label="Ordinal Features"
                  placeholder={"{"ord_feat_1": ["MALE", "FEMALE"], "ord_feat_2": ["FIRST", "SECOND", "THIRD"]}"}
                  onChange={this.handleOrdinalFeatures}
                />
              </Form.Input>
              <Form.Input
                label="Categorical Features"
              >
                <textarea
                  label="Categorical Features"
                  placeholder={"cat_feat_1, cat_feat_2"}
                  onChange={this.handleCatFeatures}
                />
              </Form.Input>
              <Popup
                header="Error Submitting Dataset"
                content={serverResp}
                open={errorMsg ? true : false}
                trigger={
                  <Button
                    inverted
                    color="blue"
                    compact
                    size="small"
                    icon="upload"
                    content="Upload dataset"
                    onClick={this.handleUpload}
                  />
                }
              />
            </div>
          </Segment>
        </Form>
        {dataPrevTable}
      </div>
    );
  }

  

Это довольно простой компонент react, подключенный к app redux store с некоторыми семантическими элементами пользовательского интерфейса для приятного стиля. Есть форма с вводом файла, вводом текстовой области и кнопкой / всплывающим окном; если файл не выбран, отображается только ввод файла, если попытка отправки файла не увенчалась успехом, во всплывающем окне отображается сообщение об ошибке.

Для пункта 1 — я попытался следовать руководству / документам из jest и создать макет api, который использует компонент, и использовать jest.mock()

 jest.mock('../../utils/apiHelper');
import uploadDataset from '../../data/datasets/dataset/api';
  

But in the unit test itself when I try

   it('testing promise for successfully case, proper dependent_col', () => {
    expect.assertions(1);
    return uploadDataset(fakeDataset).then(data => expect(data.name).toEqual('iris.csv'));
  })
  

тест завершается с ошибкой TypeError: (0 , _api2.default) is not a function
Я прибегнул к прямому импорту макетного api и его непосредственному вызову, который работает нормально, но не тестирует модуль, который должен выполнять вызов api; Я чувствую, что это почти спорный вопрос, чтобы протестировать эту функциональность таким образом, и предпочел бы протестировать компонент таким образом, чтобы правильно эмулироватькак это работает на самом деле, т.Е. Запускать событие, которое вызовет вызов api, вместо того, чтобы просто вызывать api напрямую. Как это jest.mock() должно быть использовано? И я использую его неправильно?

Для пункта 2 — я напрямую управляю состоянием реакции компонента и проверяю наличие обновлений пользовательского интерфейса, а не имитирую ввод данных пользователем через поля формы. Я пытаюсь получить доступ к полю ввода файла с помощью функции поиска enzyme

 let testFileUpload;

beforeEach(() => {
  testFileUpload = mount(<FileUpload store={store} testProp="hello" />);
})

... other tests ...

it('click file upload button', () => {
 testFileUpload.find('#upload_dataset_file_browser_button').simulate("change", 
 {
       target: {
         files: [ 'iris.csv' ]
       }
     });
 })
}
  

и тест завершается с ошибкой Method “simulate” is meant to be run on 1 node. 2 found instead.
Как я должен получить доступ к элементу ввода файла / html по идентификатору и / или что ожидает enzyme с помощью simulate? Я провел последние несколько дней, просматривая документацию и примеры jest / enzyme, и мне нужна помощь. Почему метод find возвращает 2 узла, когда есть только один элемент с этим идентификатором? И как я должен имитировать событие изменения для выбора файла на указанном узле?
Подобно вышеупомянутому обходному пути, я напрямую изменяю состояние компонента и проверяю его с этого момента, но я бы предпочел имитировать реальные варианты использования как можно ближе.

Любая помощь приветствуется.

Учитывая все сказанное, можно ли обойти определенные вещи в модульных тестах, чтобы по существу добиться тех же результатов? С моей проблемой у меня возникли некоторые проблемы с имитацией ввода в поле формы, которое должно использовать react setState при вводе / изменении пользователем, поэтому я просто меняю состояние напрямую вместо получения ввода через элемент пользовательского интерфейса. По мнению людей, знакомых с модульным тестированием, достаточно ли такого способа модульного тестирования или это грубо неверно? Прямо сейчас я практически единственный, кто проводит модульные тесты для интерфейсных компонентов пользовательского интерфейса в этом проекте, и я не слишком уверен, как правильно протестировать эти части.

Ответ №1:

Не уверен, почему элемент с #id монтируется дважды. Но вы можете использовать index:

 testFileUpload.find('#upload_dataset_file_browser_button').at(0).simulate("change")
  

Раньше я думал, что enzyme работает так же, как jquery работает с селекторами css. Но, похоже, это не так. Даже создатели библиотеки не рекомендуют использовать mount с имитацией действий. Но я все равно использую, потому что это лучший способ обеспечить хорошее покрытие моих тестов. В enzyme я в основном использую селектор элементов, а не селекторы css.

Я использую prop для имитации действий, потому что simulate на самом деле не может «имитировать» пользовательские действия. Попробуйте это:

 testFileUpload.find(Input).prop('onChange')('paramatertosendtofunction')
  

Редактировать:
вы можете использовать вспомогательную функцию

 function flushPromises() {
  return new Promise(resolve => setImmediate(resolve));
}


test('', () => {
  testFileUpload.find(Input).prop('onChange')('paramatertosendtofunction')
  testFileUpload.update()
  return flushPromises().then(() => {
    expect(textFileUpload.state.foo).toBe('bar')
  });
})
  

Комментарии:

1. Спасибо, это очень хороший совет. Привязка к использованию первого метода для имитации действия работает не слишком хорошо, и функция обработчика событий для ввода файла не запускается; использование 2-го предложения работает, и обработчик событий получает поддельный файл. Однако обработчик события ввода файла использует setState react, который, похоже, работает не так, как я ожидаю. После выбора поддельного файла я пытаюсь просмотреть состояние компонента через enzyme с testFileUpload.state() помощью и даже после ручного выполнения testFileUpload.update() состояния неверно

2. Это может быть проблема с синхронизацией из-за асинхронных действий. К тому времени, когда вы сделаете утверждение, может быть слишком рано. Я отредактировал свой ответ. Взгляните на нижнюю часть. Немного грязного обходного пути. Не уверен, что это сработает в вашем случае, но попробуйте.

3. Спасибо за обновление, пытался использовать вспомогательную функцию с setImmediate, чтобы дождаться разрешения обещания, прежде чем проверять состояние реакции компонента, но не повезло, состояние не определено после разрешения вспомогательной функции