如何对 fetch 完成后呈现的 React 组件进行单元测试?
Posted
技术标签:
【中文标题】如何对 fetch 完成后呈现的 React 组件进行单元测试?【英文标题】:How to unit test a React component that renders after fetch has finished? 【发布时间】:2018-01-08 16:42:31 【问题描述】:我是 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()
(在componentDidMount()
)完成并且链式 Promise 完成执行之后。
我的测试代码是这样的:
describe('MyComponent', () =>
fetchMock.get('*', some_response);
it('renders something', () =>
let wrapper = mount(<MyComponent />);
expect(wrapper.find(...)).to.have.something();
;
无论我从it
返回什么,它都会在render()
第一次执行之后但第二次之前运行。例如,如果我返回 fetchMock.flush().then(() => expect(...))
,则返回的 Promise 在第二次调用 render()
之前执行(我相信我能理解为什么)。
如何等到第二次调用 render()
后再运行 expect()
?
【问题讨论】:
在我看来,您试图在一次测试中测试太多东西。您要测试的是,您的 fetch 函数在组件挂载时被调用,然后您有多个其他测试将状态显式传递给它们,您可以检查组件是否正确呈现。 @MattWatson 如果我检查 (1) 是否调用了 fetch 函数并且 (2) 是否正确传递了状态,那么我无法检查 (1.5) 状态是否设置正确。如何检查状态是否设置正确? 【参考方案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();
);
);
当涉及到测试时,您需要保持简单,如果您的组件难以测试,那么您的设计就有问题;)
【讨论】:
有没有可能按照我的原意去做?我问是为了更好地掌握异步编程。 Jest 用于单元测试,您尝试做的不是单元测试。话虽如此,您可以从测试中手动设置状态,例如wrapper.setState( some_state )
然后expect(wrapper.find(...)).to.have.something();
。要测试 fetch... 除了将其从组件中取出之外,这里没有太多选择;)【参考方案2】:
我找到了一种方法来做我最初要求的事情。我没有意见(还)它是否是好的策略(事实上我必须在之后立即重构组件,所以这个问题与我正在做的事情不再相关)。无论如何,这是测试代码(解释如下):
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 向我解释了手动解决承诺的技巧。
【讨论】:
那是个坏消息。我希望这不是唯一的方法!【参考方案3】:我在这方面取得了一些成功,因为它不需要包装或修改组件。然而,假设组件中只有一个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();
);
【讨论】:
太丑了……这仍然是最先进的吗? 不确定,因为我最近在这方面做得不多,但你不喜欢怎么办? 倒计时和计时器。如果有一些processAllPromises
或其他东西会很好。我最终在 react 测试库中使用了findBy
(我正在测试 React):testing-library.com/docs/dom-testing-library/api-queries/… 它仍然有一个计时器/超时,它可以像这样实现!但至少我不必写或看到它:-D
您不必使用倒计时/计时器,它只是作为备份,以避免在承诺永远不会完成时 CI/CD 服务器挂起。您可以搜索“flush promises”,详细了解如何确保处理所有承诺。以上是关于如何对 fetch 完成后呈现的 React 组件进行单元测试?的主要内容,如果未能解决你的问题,请参考以下文章
无法使用 fetch POST 方法对未安装的组件执行 React 状态更新
React Update Fetch on Checkbox Click