8

我是 Jest/React 初学者。开玩笑地说it,我需要等到所有承诺都执行完毕后再进行实际检查。

我的代码与此类似:

export class MyComponent extends Component {
    constructor(props) {
        super(props);
        this.state = { /* Some state */ };
    }

    componentDidMount() {
        fetch(some_url)
            .then(response => response.json())
            .then(json => this.setState(some_state);
    }

    render() {
        // Do some rendering based on the state
    }
}

当组件被挂载时,render()运行两次:一次在构造函数运行后,一次在fetch()(in componentDidMount()) 完成并且链式 Promise 完成执行后)。

我的测试代码与此类似:

describe('MyComponent', () => {

    fetchMock.get('*', some_response);

    it('renders something', () => {
        let wrapper = mount(<MyComponent />);
        expect(wrapper.find(...)).to.have.something();
    };
}

无论我从什么返回it,它都会在第一次render()执行之后但在第二次之前运行。例如,如果我 return fetchMock.flush().then(() => expect(...)),则返回的承诺在第二次调用之前执行render()(我相信我能理解为什么)。

我怎样才能等到第二次render()被调用再运行expect()

4

3 回答 3

1

我会分开关注点,主要是因为更容易维护和测试。我不会在组件内部声明 fetch,而是在其他地方进行,例如在 redux 操作中(如果使用 redux)。

然后分别测试 fetch 和组件,毕竟这是单元测试。

对于异步测试,您可以在done测试中使用参数。例如:

describe('Some tests', () => {
  fetchMock.get('*', some_response);

  it('should fetch data', (done) => { // <---- Param
    fetchSomething({ some: 'Params' })
      .then(result => {
        expect(result).toBe({ whatever: 'here' });
        done(); // <--- When you are done
      });
  });
})

你可以通过在 props 中发送加载的数据来测试你的组件。

describe('MyComponent', () => {

  it('renders something', () => {
    const mockResponse = { some: 'data' };
    let wrapper = mount(<MyComponent data={mockResponse}/>);

    expect(wrapper.find(...)).to.have.something();
  });
});

当涉及到测试时,您需要保持简单,如果您的组件难以测试,那么您的设计就有问题;)

于 2017-08-01T17:38:03.553 回答
1

我在这方面取得了一些成功,因为它不需要包装或修改组件。然而,它假设组件中只有一个fetch(),但如果需要,它可以很容易地修改。

// testhelper.js

class testhelper
{
    static async waitUntil(fnWait) {
        return new Promise((resolve, reject) => {
            let count = 0;
            function check() {
                if (++count > 20) {
                    reject(new TypeError('Timeout waiting for fetch call to begin'));
                    return;
                }
                if (fnWait()) resolve();
                setTimeout(check, 10);
            }
            check();
        });
    }

    static async waitForFetch(fetchMock)
    {
        // Wait until at least one fetch() call has started.
        await this.waitUntil(() => fetchMock.called());

        // Wait until active fetch calls have completed.
        await fetchMock.flush();
    }
}

export default testhelper;

然后你可以在你的断言之前使用它:

import testhelper from './testhelper.js';

it('example', async () => {
    const wrapper = mount(<MyComponent/>);

    // Wait until all fetch() calls have completed
    await testhelper.waitForFetch(fetchMock);

    expect(wrapper.html()).toMatchSnapshot();
});
于 2018-07-02T08:45:50.933 回答
0

我找到了一种方法来做我最初要求的事情。我没有意见(还)它是否是好的策略(事实上我必须在之后立即重构组件,所以这个问题不再与我正在做的事情相关)。无论如何,这是测试代码(下面的解释):

import React from 'react';
import { mount } from 'enzyme';
import { MyComponent } from 'wherever';
import fetchMock from 'fetch-mock';

let _resolveHoldingPromise = false;

class WrappedMyComponent extends MyComponent {

    render() {
        const result = super.render();
        _resolveHoldingPromise && _resolveHoldingPromise();
        _resolveHoldingPromise = false;
        return result;
    }

    static waitUntilRender() {
        // Create a promise that can be manually resolved
        let _holdingPromise = new Promise(resolve =>
            _resolveHoldingPromise = resolve);

        // Return a promise that will resolve when the component renders
        return Promise.all([_holdingPromise]);
    }
}

describe('MyComponent', () => {

    fetchMock.get('*', 'some_response');

    const onError = () => { throw 'Internal test error'; };

    it('renders MyComponent appropriately', done => {
        let component = <WrappedMyComponent />;
        let wrapper = mount(component);
        WrappedMyComponent.waitUntilRender().then(
            () => {
                expect(wrapper.find('whatever')).toBe('whatever');
                done();
            },
            onError);
    });
});

主要思想是,在测试代码中,我将组件子类化(如果这是 Python,我可能会对其进行猴子补丁,在这种情况下或多或少地工作方式相同),以便它的render()方法发送一个信号,表明它执行。发送信号的方式是手动解决一个promise。当一个promise被创建时,它会创建两个函数,resolve和reject,当被调用时会终止promise。让 Promise 之外的代码解析 Promise 的方法是让 Promise 将对其解析函数的引用存储在外部变量中。

感谢 fetch-mock 作者 Rhys Evans,他向我解释了手动解决承诺的技巧。

于 2017-08-09T07:52:40.803 回答