简体   繁体   English

如何通过开玩笑和酶测试框架访问内部的反应组件和模拟api调用?

[英]How to access internal pieces of react component and mock api call with jest & enzyme testing framework?

I am making a javascript unit test suite for a react-redux component with jest & enzyme; 我正在为一个带有jest和酶的react-redux组件制作一个javascript单元测试套件; the component in question is a file upload form with a few fields that has some basic styling changes based on user input and/or server response. 有问题的组件是一个文件上载表单,其中包含一些字段,这些字段根据用户输入和/或服务器响应进行了一些基本样式更改。 Jest & enzyme are both set up and working and I am able to successfully write basic tests okay. Jest和酶都已设置好并且正常工作,我能够成功地编写基本测试。

I want to simulate user interaction with these UI elements (a file input and text fields) using jest & enzyme but I am having difficulty in doing so; 我想使用jest和酶来模拟用户与这些UI元素(文件输入和文本字段)的交互,但我很难这样做; I am fairly new to using these testing frameworks and have a basic understanding of react-redux and I am a bit overwhelmed with making sure the proper pieces are in place in order to write meaningful unit tests. 我对使用这些测试框架并且对react-redux有一个基本的了解是相当新的,我有点不知所措,以确保正确的部分到位,以便编写有意义的单元测试。

A general aside: this unit test is being made after the fact, some months ago I joined this project that is using test driven development techniques and I have not developed that way before so I am using this as an example/template for future development. 一般情况下:这个单元测试是在事实之后进行的,几个月前我加入了这个使用测试驱动开发技术的项目,我之前没有开发过,所以我将它作为未来开发的示例/模板。

As of now the unit tests create the component successfully (with enzyme's mount & shallow with a mockStore and initial state). 截至目前,单元测试成功地创建了组件(使用酶的mountshallow的mockStore和初始状态)。 I have tried to do two things: 我试过做两件事:

  1. create a __mocks__ api just for unit testing which a different module uses (a redux action reducer which uses a helper method that makes the api call) as per jest docs here 根据jest docs创建一个__mocks__ api,用于不同模块使用的单元测试(一个使用辅助方法进行api调用的redux动作减少器)
  2. find/access form fields (file input & text area) to simulate user interaction 查找/访问表单字段(文件输入和文本区域)以模拟用户交互

I have not been fully successful in both cases and have unit tests that cover aspects of these but not in a fully integrated manner. 我在这两种情况下都没有完全成功,并且有单元测试,涵盖了这些方面,但没有完全集成的方式。

here is the render method for the component I am trying to make unit tests for: 这是我试图进行单元测试的组件的render方法:

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

It's a fairly basic react component connected to the app redux store with some semantic ui stuff for nice styling. 它是一个相当基本的反应组件,连接到app redux商店,带有一些语义ui的东西,可以很好地造型。 There's a form with a file input, text area inputs and a button/popup; 有一个带有文件输入,文本区域输入和按钮/弹出窗口的表单; if no file is selected only the file input is displayed, if the attempt to submit a file is not successful the error message is displayed in the popup. 如果未选择任何文件,则仅显示文件输入,如果提交文件的尝试不成功,则弹出窗口中将显示错误消息。

For point 1 - I attempted to follow the guide/docs from jest and create the mock api piece that a component uses and use jest.mock() 对于第1点 - 我试图遵循jest中的指南/ docs并创建组件使用的模拟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'));
  })

the test fails with TypeError: (0 , _api2.default) is not a function I resorted to directly importing the mock api and calling it directly which works fine but fails to test the module which is supposed to be making the api call; 测试因TypeError: (0 , _api2.default) is not a function失败TypeError: (0 , _api2.default) is not a function我直接导入mock api并直接调用它TypeError: (0 , _api2.default) is not a function ,它工作正常但无法测试应该进行api调用的模块; I feel like it's almost a moot point to test this functionality in this way and would prefer to test the component in a way that properly emulates how it actually works, ie trigger the event which would cause the api call instead of just calling the api directly. 我觉得以这种方式测试这个功能几乎是一个没有实际意义,并且更愿意以正确模拟它实际工作方式的方式测试组件,即触发导致api调用的事件而不是直接调用api 。 How is jest.mock() supposed to be used? 应该如何使用jest.mock() And am I using it incorrectly? 我不正确地使用它?

For point 2 - I am manipulating component react state directly and checking for UI updates as opposed to simulating user input through the form fields. 对于第2点 - 我正在直接操作组件反应状态并检查UI更新,而不是通过表单字段模拟用户输入。 I try to access the file input field using enzyme's find 我尝试使用酶的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' ]
       }
     });
 })
}

and the test fails with an error Method “simulate” is meant to be run on 1 node. 2 found instead. 并且测试失败并出现错误Method “simulate” is meant to be run on 1 node. 2 found instead. Method “simulate” is meant to be run on 1 node. 2 found instead. How am I supposed to access a file input/html element by ID and/or what does enzyme expect with simulate? 我应该如何通过ID访问文件输入/ html元素和/或酶对模拟的期望是什么? I have spent the last few days looking at jest/enzyme documentation and examples and need some help. 我花了最近几天查看jest / enzyme文档和示例,需要一些帮助。 Why does the find method return 2 nodes when there is only one element with that id? 当只有一个具有该id的元素时,为什么find方法返回2个节点? And how am I supposed to simulate a change event for a file selection on said node? 我应该如何在所述节点上模拟文件选择的更改事件? Similar to the above mentioned workaround, I am changing the component's state directly and checking things from that point on but I would prefer to simulate actual use cases as close as possible. 与上面提到的解决方法类似,我正在直接更改组件的状态并从该点检查事物,但我更愿意尽可能地模拟实际用例。

Any help is greatly appreciated. 任何帮助是极大的赞赏。

With all that said, is it okay to circumvent certain things in unit tests to essentially accomplish the same results? 尽管如此,是否可以绕过单元测试中的某些事情来基本上完成相同的结果? With my problem, I am having some trouble simulating input into a form field which is supposed to use react setState on user input/change so I am just changing the state directly in lieu of getting the input through the UI element. 有了我的问题,我在模拟输入到表单字段时遇到了一些麻烦,该表单字段应该在用户输入/更改时使用react setState,所以我只是直接更改状态而不是通过UI元素获取输入。 In the opinion of people familiar with unit testing, is this manner of unit test sufficient or is this grossly incorrect? 熟悉单元测试的人认为,这种单元测试方式是否足够或者这是非常不正确的? Right now I am pretty much the only person making unit tests for the front end UI components on this project and I am not too sure how to correctly test these pieces. 现在我几乎是唯一一个为这个项目的前端UI组件进行单元测试的人,我不太确定如何正确测试这些部分。

Not sure why an element with an #id is mounted twice. 不确定为什么带有#id的元素被挂载两次。 But you can use index: 但你可以使用索引:

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

I used to think enzyme works same as jquery works with css selectors. 我曾经认为酶的工作原理与jquery与css选择器相同。 But seems it's not. 但似乎不是。 Even the creators of the library do not recommend using mount with simulating actions. 即使是库的创建者也不建议使用带模拟操作的mount。 But I use anyway because that's the best way I can have good coverage on my tests. 但我仍然使用,因为这是我在测试中获得良好报道的最佳方式。 With enzyme I mostly use element selector rather than css selectors. 使用酶我主要使用元素选择器而不是css选择器。

I use prop to simulate actions, because simulate cannot really "simulate" custom actions. 我使用prop来模拟动作,因为模拟不能真正“模拟”自定义动作。 Try this: 尝试这个:

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

EDIT: you might use a helper function 编辑:您可以使用辅助函数

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM