当模拟点击调用一个调用 promise 的函数时,使用 React 的 Jest 和 Enzyme 进行测试

IT技术 asynchronous reactjs promise jestjs enzyme
2021-04-14 04:23:04
  • react v15.1.0
  • 玩笑 v12.1.1
  • 酵素 v2.3.0

我正在尝试弄清楚如何测试在单击调用的函数中调用 promise 的组件。我期待 Jest 的runAllTicks()功能在这里帮助我,但它似乎并没有执行Promise。

零件:

import React from 'react';
import Promise from 'bluebird';

function doSomethingWithAPromise() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, 50);
  });
}

export default class AsyncTest extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      promiseText: '',
      timeoutText: ''
    };

    this.setTextWithPromise = this.setTextWithPromise.bind(this);
    this.setTextWithTimeout = this.setTextWithTimeout.bind(this);
  }

  setTextWithPromise() {
    return doSomethingWithAPromise()
      .then(() => {
        this.setState({ promiseText: 'there is text!' });
      });
  }

  setTextWithTimeout() {
    setTimeout(() => {
      this.setState({ timeoutText: 'there is text!' });
    }, 50);
  }

  render() {
    return (
      <div>
        <div id="promiseText">{this.state.promiseText}</div>
        <button id="promiseBtn" onClick={this.setTextWithPromise}>Promise</button>
        <div id="timeoutText">{this.state.timeoutText}</div>
        <button id="timeoutBtn" onClick={this.setTextWithTimeout}>Timeout</button>
      </div>
    );
  }
}

和测试:

import AsyncTest from '../async';
import { shallow } from 'enzyme';
import React from 'react';

jest.unmock('../async');

describe('async-test.js', () => {
  let wrapper;

  beforeEach(() => {
    wrapper = shallow(<AsyncTest />);
  });

  // FAIL
  it('displays the promise text after click of the button', () => {
    wrapper.find('#promiseBtn').simulate('click');

    jest.runAllTicks();
    jest.runAllTimers();

    wrapper.update();

    expect(wrapper.find('#promiseText').text()).toEqual('there is text!');
  });

  // PASS
  it('displays the timeout text after click of the button', () => {
    wrapper.find('#timeoutBtn').simulate('click');

    jest.runAllTimers();

    wrapper.update();

    expect(wrapper.find('#timeoutText').text()).toEqual('there is text!');
  });
});
2个回答

更新的答案:使用async/await导致代码更清晰。下面的旧代码。

我通过结合以下元素成功解决了这个问题:

  • 模拟Promise并立即解决
  • 通过标记测试函数使测试异步 async
  • 模拟点击后,等待下一个宏任务给promise时间解决

在您的示例中,这可能如下所示:

// Mock the promise we're testing
global.doSomethingWithAPromise = () => Promise.resolve();

// Note that our test is an async function
it('displays the promise text after click of the button', async () => {
    wrapper.find('#promiseBtn').simulate('click');
    await tick();
    expect(wrapper.find('#promiseText').text()).toEqual('there is text!');
});

// Helper function returns a promise that resolves after all other promise mocks,
// even if they are chained like Promise.resolve().then(...)
// Technically: this is designed to resolve on the next macrotask
function tick() {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  })
}

update()使用这种方法时,Enzyme既不充分也不需要,因为 Promise 永远不会在它们被创建的同一个滴答中解析——这是设计的。有关此处发生的事情的非常详细的说明,请参阅此问题

原始答案:相同的逻辑,但稍微不那么漂亮。使用 Node'ssetImmediate将测试推迟到下一个滴答声,也就是Promise将解决的时间。然后调用 Jestdone异步完成测试。

global.doSomethingWithAPromise = () => Promise.resolve({});

it('displays the promise text after click of the button', (done) => {
    wrapper.find('#promiseBtn').simulate('click');

  setImmediate( () => {
    expect(wrapper.find('#promiseText').text()).toEqual('there is text!');
    done();
  })
});

这不是很好,因为如果您必须等待多个Promise,您将获得大量嵌套回调。

答对了!setImmediate是我需要在我的回调函数中使用的,它是由我的组件的componentDidUpdate生命周期方法之一触发的
2021-06-02 04:23:04
我有一个类似的问题,但我用它解决了它setTimeout(() => { ... }, 0)由于某种原因setImmediate,测试失败甚至yarn test命令突然退出。
2021-06-05 04:23:04
这次真是万分感谢!done()在 setImmediate 之外调用,但这种组合有效并防止测试套件挂起。
2021-06-09 04:23:04
setImmediate()除非立即解决Promise,否则将无法工作。所以你可能想嘲笑它。
2021-06-12 04:23:04
该解决方案看起来很有希望,但在我的情况下 setImmediate 没有任何效果。Promise被简单地忽略,它的后果不会被等待。我需要模拟我想要等待的 Promise 还是有一种方法可以简单地等待一般内部使用的 Promise?
2021-06-19 04:23:04

在结束测试之前不需要以某种方式等待Promise的履行。我可以从您的代码中看到两种主要方法。

  1. 独立测试那个onClick和你的Promise方法。所以检查onClick调用了正确的函数,但监视setTextWithPromise,触发点击并断言setTextWithPromise被调用。然后,您还可以获取组件实例并调用该方法,该方法返回您可以附加处理程序并断言它做了正确的事情的Promise。

  2. 公开一个你可以传入的回调props,当Promise解决时会调用它。