React Enzyme - 测试 `componentDidMount` 异步调用

IT技术 javascript reactjs enzyme
2021-04-19 19:47:31

每个人。

componentDidMount.

这是我的组件代码:

'use strict';


import React from 'react';
import UserComponent from './userComponent';
const request = require('request');


class UsersListComponent extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      usersList: []
    };
  }

  componentDidMount() {
    request('https://api.github.com/users', (err, res) => {
      if (!err && res.statusCode === 200) {
        this.setState({
          usersList: res.slice(0)
        });
      }
      else {
        console.log(err);
      }
    });
  }

  render() {
    if (!this.state.usersList.length) {
      return null;
    }

    return (
      <div className="users-list">
        { this._constructUsersList() }
      </div>
    );
  }

  _constructUsersList() {
    return this.state.usersList.map((user, index) => {
      return (
        <UserComponent
              key={ index }
              name={ user.name }
              age={ user.age } />
      );
    });
  }
};


export default UsersListComponent;

现在,我在我的测试文件中所做的(我有一个由 Mocha + Chai + Sinon 组成的设置,一切正常):

import React from 'react';
import { expect } from 'chai';
import { shallow, mount, render } from 'enzyme';
import sinon from 'sinon';
import UsersListComponent from '../src/usersListComponent';


describe('Test suite for UsersListComponent', () => {
  it('Correctly updates the state after AJAX call in `componentDidMount` was made', () => {
    const server = sinon.fakeServer.create();
    server.respondWith('GET', 'https://api.github.com/users', [
      200,
      {
        'Content-Type': 'application/json',
        'Content-Length': 2
      },
      '[{ "name": "Reign", "age": 26 }]'
    ]);
    let wrapper = mount(<UsersListComponent />);
    server.respond();
    server.restore();
    expect(wrapper.update().state().usersList).to.be.instanceof(Array);
    console.log(wrapper.update().state().usersList.length);
  });
});

即使我调用update()包装器,状态也不会更新长度仍然是 0。我在这里遗漏了什么吗?我是否需要以另一种方式模拟服务器响应?

感谢您的帮助!

3个回答

您可以通过传递一个返回 Promise 的函数来将用户列表检索从 react 组件中抽象出来,而不是

  componentDidMount() {
    request('https://api.github.com/users', (err, res) => {
      if (!err && res.statusCode === 200) {
        this.setState({
          usersList: res.slice(0)
        });
      }
      else {
        console.log(err);
      }
    });
  }

将其替换为

  componentDidMount() {
     var comp = this;
     this.props.getUsers()
        .then(function(usersList) {
          comp.setState({
            usersList: usersList
          });
        })
        .catch(function (err) {
            console.log(err);
        });
  }

在您的测试中模拟该getUsers功能:

    it('Correctly updates the state after AJAX call in `componentDidMount` was made', (done) => {

      let resolveGetUsers;

      let getUsers = function() {
        return new Promise(function (resolve, reject) {
                  resolveGetUsers = resolve;
                });
      }

      let wrapper = mount(<UsersListComponent getUsers={getUsers} />);

      resolveGetUsers([{ "name": "Reign", "age": 26 }]);


      // promise resolve happens in a subsequent event loop turn so move assertions inside setImmediate
      setImmediate(() => {

        expect(wrapper.update().state().usersList).to.be.instanceof(Array);
        ...

        done();
      });
    }

请注意,我已经这样做了,它对我有用(即使没有 wrapper.update() 部分),在这里我尝试将它应用于您的代码示例而不运行它..

另请注意,它也应该适用于除 componentDidMount 之外的其他情况 - 例如在单击按钮后触发异步操作..

请注意,如果在 中使用beforeEach(),该setImmediate()部分可能会导致错误,因为其内容的执行被推迟。您的测试可能会在setImmediate()完成之前开始执行为了解决这个问题,将setImmediate()包装在一个new Promise()块中的所有语句都完成时才解决。使beforeEach()函数异步并在那里有一行,例如:await new Promise((resolve, reject) => { /* setImmediateBlock goes here; don't forget to put resolve() at the end of it. */ });
2021-06-10 19:47:31

给你我的解决方案:

  • 酶 3.9.0
  • 酶适配器react 16 1.9.1
  • react 16.8.0
  • 笑话

1- 模拟数据服务

您必须模拟数据服务(通常类似于 axios 或其他 HTTP 库)。

文件__mocks__/LogDataService.js::

let searchData = {}

const LogDataService = {
    search: () => new Promise((resolve, reject) =>
        process.nextTick(() => resolve(searchData))
    ),
    setSearchData: (data) => searchData = data
}

export default LogDataService

2- 测试

文件__tests__/toto.test.js::

jest.mock('services/LogDataService')

import LogDataService from 'services/LogDataService';

// Declare a super promise that will synchro all promises
function flushPromises() {
    return new Promise(resolve => setImmediate(resolve));
}

it('renders without data', async () => {
    // Set dummy data
    LogDataService.setSearchData([])
    // The component we want to test that use
    // LogDataService.search method we just mock up
    const component = mount(<JobsExecutionTimeline />);
    // wait
    await flushPromises();
    // re-build the render
    component.update()
    // Compare to snapshot
    expect(toJson(component)).toMatchSnapshot();
});
非常好await new Promise(setImmediate),如果您愿意,您可以将其缩短为并删除辅助功能。
2021-06-20 19:47:31

我查看了https://www.npmjs.com/package/request并发现回调中缺少“body”参数。

它应该看起来像

...
request('https://api.github.com/users', (err, res, body) => {
    if (!err && res.statusCode === 200) {
      this.setState({
        usersList: body.slice(0)
      });
    }
...
也用于nock模拟服务器响应。sinon 中的假服务器有一些奇怪的问题。请求没有被捕获。我可能需要更深入地研究(也许我在那里做了一些时髦的事情)。
2021-06-13 19:47:31
确实如此。我肯定有这个问题,但它并没有解决真正的问题。所以我最终解决了它。事实证明,由于其异步性质,我不得不在超时时检查 setState。这里的最终实现 - github.com/r31gN/tdd-react-enzyme/blob/master/tests/...(我也切换到superagent,只是个人喜好)。
2021-06-17 19:47:31