Jest/Enzyme 错误:“方法 'setState' 仅用于在单个节点上运行.找到了 3 个." [英] Jest/Enzyme Error: "Method 'setState' is only meant to run on a single node. 3 found instead."

查看:12
本文介绍了Jest/Enzyme 错误:“方法 'setState' 仅用于在单个节点上运行.找到了 3 个."的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对在 React 应用程序上使用 Enzyme/Jest 进行测试还很陌生,所以我设置测试的方式可能有问题.

I'm fairly new to testing with Enzyme/Jest on a React application, so perhaps there's something wrong with the way I'm setting up my test.

我特别想在我的组件中测试单个函数:reviewCandidate()

I specifically want to test a single function in my component: reviewCandidate()

reviewCandidate() {
  const { candidate_id: candidateId, } = this.props.match.params;
  const { latest_application_id: latestApplication, } = this.state.candidateInfo;
  const { expressApi, } = this.props;
  /** @todo Define user when authentication is in place */
  const user = 'undefined';
  expressApi.addReviewedCandidate(
    parseInt(candidateId, 10),
    latestApplication,
    user,
    // comment,
  ).then(() => {
    // If the API call is successful, have the state reflect this accordingly
    this.setState({
      reviewInfo: {
        candidate_id: candidateId,
        last_application_reviewed: latestApplication,
        user,
        // comment: comment
      },
    });
    // Pop-up a snackbar indicating a successful review
    this.props.setSnackbar({
      open: true,
      message: <span>Candidate_{candidateId} marked as reviewed</span>,
    });
  });
}

在使用 Web 应用程序时,它可以正常工作而不会发出任何警告.当我尝试使用 Jest/Enzyme 对其进行测试时出现问题.

It works without any warnings when using the web application. The issue arises when I try to test it using Jest/Enzyme.

这是我为上述函数编写的测试:

Here is the test I have written for the above function:

describe('Reviewing Candidates', () => {
  const mockCandidateReviewStatus = [];
  const mockApplicationsOfCandidate = {/* mock data */};

  const mockExpressApi = {
    applicationsOfCandidate() {
      return new Promise(resolve => resolve(mockApplicationsOfCandidate));
    },
    candidateReviewStatus() {
      return new Promise(resolve => resolve(mockCandidateReviewStatus));
    },
    addReviewedCandidate() {
      return new Promise(resolve => resolve());
    },
  };
  const handleError = error => error;
  it('ensures reviewedCandidates() is called correctly', async() => {
    const setSnackbar = jest.fn(() => 'foobar');
    const wrapper = shallow(
      <Candidate
        expressApi={mockExpressApi}
        handleError={handleError}
        match={{
          params: {
            candidate_id: 153,
          },
        }}
        utils={utils}
        setSnackbar={setSnackbar}
      />
    );
    /**
     * These wrapper.update() calls ensure componentDidMount() finish setting
     * the state of the component
     *
     * Unsure if this is the best way to deal with waiting for 
     * componentDidMount() to finish
     */
    await wrapper.update();
    await wrapper.update();
    await wrapper.instance().reviewCandidate();
    /**
     * @todo: Check this.state.reviewInfo is set as expected
     */
    expect(setSnackbar).toHaveBeenCalled();
  });
});

但是 await wrapper.instance().reviewCandidate() 调用使酶崩溃.

But the await wrapper.instance().reviewCandidate() call crashes Enzyme.

Error: Method "setState" is only meant to be run on a single node. 3 found instead.
    at ShallowWrapper.single (/path/to/project/node_modules/enzyme/build/ShallowWrapper.js:1828:17)
    at ShallowWrapper.setState (/path/to/project/node_modules/enzyme/build/ShallowWrapper.js:575:14)
    at Candidate.ShallowWrapper.instance.setState (/path/to/project/node_modules/enzyme/build/ShallowWrapper.js:217:35)
    at expressApi.addReviewedCandidate.then (/path/to/project/src/components/Candidate.js:514:12)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)

如果我在 API 调用之后注释掉 .then() 中的 this.setState 调用:

If I comment out the this.setState call in the .then() after the API call:

reviewCandidate() {
  // ...
  expressApi.addReviewedCandidate(
    // ...
  ).then(() => {
    // If the API call is successful, have the state reflect this accordingly
    /*
    this.setState({
      reviewInfo: {
        candidate_id: candidateId,
        last_application_reviewed: latestApplication,
        user,
        // comment: comment
      },
    });
    */
    // Pop-up a snackbar indicating a successful review
    this.props.setSnackbar({
      open: true,
      message: <span>Candidate_{candidateId} marked as reviewed</span>,
    });
  });
}

然后测试没有任何问题:

Then the test works without any issues:

 PASS  test/Candidate.test.js (15.954s)
 PASS  test/Main.test.js

Test Suites: 2 passed, 2 total
Tests:       17 passed, 17 total
Snapshots:   0 total
Time:        20.378s
Ran all test suites related to changed files.
----------------------------|----------|----------|----------|----------|-------------------|
File                        |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------------------------|----------|----------|----------|----------|-------------------|
All files                   |    31.05 |    24.58 |    23.04 |    31.88 |                   |
 src                        |        0 |        0 |        0 |        0 |                   |
  index.js                  |        0 |        0 |        0 |        0 |   1,2,3,4,5,6,7,9 |
 src/components             |    43.93 |    38.41 |    33.86 |    44.92 |                   |
  Candidate.js              |    62.41 |    60.87 |       50 |    62.59 |... 88,489,492,616 |
  Main.js                   |    92.31 |    88.89 |    79.31 |    92.21 |... 34,342,354,363 |
----------------------------|----------|----------|----------|----------|-------------------|

如果相关,这里是在测试中调用 Mock API 调用的同一组件中的 componentDidMount() 函数:

In-case it is relevant, here is the componentDidMount() function in the same component that calls the Mock API calls in the test:

componentDidMount() {
    const candidateId = this.props.match.params.candidate_id;
    Promise.all([
      this.props.expressApi.applicationsOfCandidate(candidateId),
      this.props.expressApi.candidateReviewStatus(candidateId),
    ]).then((data) => {
      const candidateInfo = data[0][this.props.match.params.candidate_id];
      const reviewInfo = data[1][0];
      this.setState({ /* Sets various state parameters based on what was returned from the API calls*/ });
    }).catch((err) => {
      this.props.handleError(err);
      this.setState({
        error: err,
      });
    });
}

我在做什么导致了错误:方法setState"只能在单个节点上运行.找到了 3 个.?

如果我需要提供有关组件本身的更多信息,请告诉我.

Please let me know if I need to provide more information about the component itself.

在此先感谢您的帮助.

推荐答案

wrapper.update() 不应该在那里.它不返回承诺并且不能被awaited.Enzyme 已经默认运行生命周期钩子,包括 componentDidMount.

wrapper.update() shouldn't be there. It doesn't return a promise and cannot be awaited. Enzyme already runs lifecycle hooks by default, including componentDidMount.

问题在于 componentDidMount 不可测试且不公开承诺.它应该被提取到一个返回一个可以链接的承诺的方法:

The problem is componentDidMount isn't testable and doesn't expose a promise. It should be extracted to a method that returns a promise that could be chained:

loadSomething() {
    const candidateId = this.props.match.params.candidate_id;

    return Promise.all([
      this.props.expressApi.applicationsOfCandidate(candidateId),
      this.props.expressApi.candidateReviewStatus(candidateId),
    ]).then((data) => {
      const candidateInfo = data[0][this.props.match.params.candidate_id];
      const reviewInfo = data[1][0];
      this.setState({ ... });
    }).catch((err) => {
      this.props.handleError(err);
      this.setState({
        error: err,
      });
    });
}

componentDidMount() {
    this.loadSomething();
}

然后它可以在一个测试中被截断:

Then it can be stubbed in one test:

jest.stub(Candidate.prototype, 'loadSomething')
shallow(<Candidate .../>);
expect(shallow.instance().loadSomething).toHaveBeenCalledTimes(1);

并直接在另一个中测试:

And tested directly in another:

shallow(<Candidate .../>, { disableLifecycleMethods: true });
await shallow.instance().loadSomething();
expect(shallow.state(...))...

这篇关于Jest/Enzyme 错误:“方法 'setState' 仅用于在单个节点上运行.找到了 3 个."的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆