Как получить доступ к внутренним компонентам реагирующего компонента и макетировать вызов API с помощью инфраструктуры тестирования на шутку и энзимы? - PullRequest
0 голосов
/ 11 апреля 2019

Я делаю комплект модульных тестов javascript для компонента «реакт-редукс» с шуткой и энзимом; рассматриваемый компонент представляет собой форму загрузки файла с несколькими полями, в которой есть некоторые базовые изменения стиля, основанные на вводе пользователя и / или ответе сервера. Jest и энзим настроены и работают, и я могу успешно написать базовые тесты.

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

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

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

  1. создать api __mocks__ только для модульного тестирования, которое использует другой модуль (редуктор действия, который использует вспомогательный метод, который делает вызов api) согласно шуткам здесь
  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 && 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>
    );
  }

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

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

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

Но в самом модульном тесте при попытке

  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 - Я манипулирую состоянием реакции компонента напрямую и проверяю обновления пользовательского интерфейса, а не симулирую пользовательский ввод через поля формы. Я пытаюсь получить доступ к полю ввода файла с помощью энзима find

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. Как мне получить доступ к элементу input / html файла по идентификатору и / или что фермент ожидает с имитацией? Я провел последние несколько дней, просматривая документацию и примеры шуток / энзимов, и мне нужна помощь. Почему метод find возвращает 2 узла, когда есть только один элемент с таким идентификатором? И как я должен имитировать событие изменения для выбора файла на указанном узле? Подобно вышеупомянутому обходному пути, я изменяю состояние компонента напрямую и проверяю вещи с этого момента, но я бы предпочел имитировать реальные варианты использования как можно ближе.

Любая помощь очень ценится.

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

1 Ответ

0 голосов
/ 12 апреля 2019

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

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

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

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

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

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

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')
  });
})
...