如何对提取完成后呈现的 React 组件进行单元测试?
How to unit test a React component that renders after fetch has finished?
我是 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()
中)完成并且链式承诺完成执行之后)。
我的测试代码类似这样:
describe('MyComponent', () => {
fetchMock.get('*', some_response);
it('renders something', () => {
let wrapper = mount(<MyComponent />);
expect(wrapper.find(...)).to.have.something();
};
}
无论我从 it
return 执行什么,它都会在第一次 render()
执行之后但在第二次执行之前运行。例如,如果我 return fetchMock.flush().then(() => expect(...))
,returned promise 在第二次调用 render()
之前执行(我相信我能理解为什么)。
如何等到 render()
在 运行 expect()
之前调用第二次?
我会分开关注,主要是因为更容易维护和测试。我不会在组件内部声明获取,而是在其他地方进行,例如在 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();
});
});
当涉及到测试时,您需要保持简单,如果您的组件难以测试,那么您的设计有问题;)
我找到了一种方法来完成我最初提出的要求。我还没有意见(还)这是否是一个好的策略(事实上我必须在之后立即重构组件,所以这个问题不再与我正在做的事情相关)。无论如何,这是测试代码(下面的解释):
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 时,它会创建两个函数,resolve 和 reject,这两个函数在调用时会终止 promise。让 promise 之外的代码解析 promise 的方法是让 promise 在外部变量中存储对其 resolve 函数的引用。
感谢 fetch-mock 的作者 Rhys Evans 向我解释了手动解决承诺的技巧。
我在这方面取得了一些成功,因为它不需要包装或修改组件。然而,假设组件中只有一个 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();
});
我是 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()
中)完成并且链式承诺完成执行之后)。
我的测试代码类似这样:
describe('MyComponent', () => {
fetchMock.get('*', some_response);
it('renders something', () => {
let wrapper = mount(<MyComponent />);
expect(wrapper.find(...)).to.have.something();
};
}
无论我从 it
return 执行什么,它都会在第一次 render()
执行之后但在第二次执行之前运行。例如,如果我 return fetchMock.flush().then(() => expect(...))
,returned promise 在第二次调用 render()
之前执行(我相信我能理解为什么)。
如何等到 render()
在 运行 expect()
之前调用第二次?
我会分开关注,主要是因为更容易维护和测试。我不会在组件内部声明获取,而是在其他地方进行,例如在 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();
});
});
当涉及到测试时,您需要保持简单,如果您的组件难以测试,那么您的设计有问题;)
我找到了一种方法来完成我最初提出的要求。我还没有意见(还)这是否是一个好的策略(事实上我必须在之后立即重构组件,所以这个问题不再与我正在做的事情相关)。无论如何,这是测试代码(下面的解释):
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 时,它会创建两个函数,resolve 和 reject,这两个函数在调用时会终止 promise。让 promise 之外的代码解析 promise 的方法是让 promise 在外部变量中存储对其 resolve 函数的引用。
感谢 fetch-mock 的作者 Rhys Evans 向我解释了手动解决承诺的技巧。
我在这方面取得了一些成功,因为它不需要包装或修改组件。然而,假设组件中只有一个 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();
});