我应该如何测试 React Hook "useEffect" 使用 Typescript 进行 api 调用?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/55388587/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How should I test React Hook "useEffect" making an api call with Typescript?
提问by Matt Berg
I'm writing some jest-enzyme tests for a simple React app using Typescript and the new React hooks.
我正在使用 Typescript 和新的 React 钩子为一个简单的 React 应用程序编写一些笑话酶测试。
However, I can't seem to properly simulate the api call being made inside the useEffect
hook.
但是,我似乎无法正确模拟在useEffect
钩子内进行的 api 调用。
useEffect
makes the api call and updates the useState
state "data" with "setData".
useEffect
进行 api 调用并useState
使用“setData”更新状态“data”。
The object "data" is then mapped into a table to its corresponding table cells.
然后对象“数据”被映射到一个表格中,以对应其相应的表格单元格。
This seems like it should be easy to tackle with a mocked api response and an enzyme mount, but I keep getting errors telling me to use act()
for component updates.
这似乎应该很容易通过模拟 api 响应和酶安装来解决,但我不断收到错误消息,告诉我act()
用于组件更新。
I tried using act()
many ways but to no avail. I've tried replacing axios
with fetch and using enzyme shallow and the react-test-library's render, but nothing seems to work.
我尝试了act()
很多方法,但都无济于事。我尝试用axios
fetch替换并使用酶浅和 react-test-library 的渲染,但似乎没有任何效果。
The component:
组件:
import axios from 'axios'
import React, { useEffect, useState } from 'react';
interface ISUB {
id: number;
mediaType: {
digital: boolean;
print: boolean;
};
monthlyPayment: {
digital: boolean;
print: boolean;
};
singleIssue: {
digital: boolean;
print: boolean;
};
subscription: {
digital: boolean;
print: boolean;
};
title: string;
}
interface IDATA extends Array<ISUB> {}
const initData: IDATA = [];
const SalesPlanTable = () => {
const [data, setData] = useState(initData);
useEffect(() => {
axios
.get(`/path/to/api`)
.then(res => {
setData(res.data.results);
})
.catch(error => console.log(error));
}, []);
const renderTableRows = () => {
return data.map((i: ISUB, k: number) => (
<tr key={k}>
<td>{i.id}</td>
<td>
{i.title}
</td>
<td>
{i.subscription.print}
{i.mediaType.digital}
</td>
<td>
{i.monthlyPayment.print}
{i.monthlyPayment.digital}
</td>
<td>
{i.singleIssue.print}
{i.singleIssue.digital}
</td>
<td>
<button>Submit</button>
</td>
</tr>
));
};
return (
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>MediaType</th>
<th>MonthlyPayment</th>
<th>SingleIssue</th>
<th/>
</tr>
</thead>
<tbody'>{renderTableRows()}</tbody>
</table>
);
};
export default SalesPlanTable;
The test:
考试:
const response = {
data: {
results: [
{
id: 249,
mediaType: {
digital: true,
print: true
},
monthlyPayment: {
digital: true,
print: true
},
singleIssue: {
digital: true,
print: true
},
subscription: {
digital: true,
print: true
},
title: 'ELLE'
}
]
}
};
//after describe
it('should render a proper table data', () => {
const mock = new MockAdapter(axios);
mock.onGet('/path/to/api').reply(200, response.data);
act(() => {
component = mount(<SalesPlanTable />);
})
console.log(component.debug())
});
I expect it to log the html of the table with the table body section rendered, I tried some async and different ways to mock axios
but I keep either getting just the table headers or the message: An update to SalesPlanTable
inside a test was not wrapped in act(...).
I looked for many hours for a resolution but can't find anything that works so I decided to muster up some courage and ask here.
我希望它记录表格的 html 并呈现表格正文部分,我尝试了一些异步和不同的模拟方式,axios
但我一直只获取表格标题或消息:SalesPlanTable
测试内部的更新没有包含在act(...).
我中寻找了很多小时的解决方案,但找不到任何有效的方法,所以我决定鼓起勇气在这里问。
采纳答案by Brian Adams
There are two issues at play here
这里有两个问题在起作用
Asynchronous call to setData
异步调用 setData
setData
gets called in a Promise
callback.
setData
在Promise
回调中被调用。
As soon as a Promise
resolves, any callbacks waiting for it get queued in the PromiseJobs queue. Any pending jobs in the PromiseJobs queue run after the current message has completed and before the next one begins.
一旦Promise
解决,任何等待它的回调都会在PromiseJobs queue 中排队。PromiseJobs 队列中的所有挂起作业在当前消息完成之后和下一个开始之前运行。
In this case the currently running message is your test so your test completes before the Promise
callback has a chance to run and setData
isn't called until afteryour test completes.
在这种情况下,当前正在运行的消息是您的测试,因此您的测试在Promise
回调有机会运行之前完成,并且setData
直到您的测试完成后才会被调用。
You can fix this by using something like setImmediate
to delay your assertions until after the callbacks in PromiseJobs have a chance to run.
您可以通过使用诸如setImmediate
将断言延迟到 PromiseJobs 中的回调有机会运行之类的方法来解决此问题。
Looks like you'll also need to call component.update()
to re-render the component with the new state. (I'm guessing this is because the state change happens outside of an act
since there isn't any way to wrap that callback code in an act
.)
看起来您还需要调用component.update()
以使用新状态重新渲染组件。(我猜这是因为状态更改发生在 之外,act
因为没有任何方法可以将该回调代码包装在act
.)
All together, the working test looks like this:
总之,工作测试如下所示:
it('should render a proper table data', done => {
const mock = new MockAdapter(axios);
mock.onGet('/path/to/api').reply(200, response.data);
const component = mount(<SalesPlanTable />);
setImmediate(() => {
component.update();
console.log(component.debug());
done();
});
});
Warning: An update to ... was not wrapped in act(...)
警告:对 ... 的更新未包含在 act(...)
The warning is triggered by state updates to the component that happen outside of an act
.
该警告由在act
.
State changes caused by asynchronous calls to setData
triggered by a useEffect
functionwill always happen outside of an act
.
由函数触发的异步调用setData
useEffect
引起的状态更改将始终发生在act
.
Here is an extremely simple test that demonstrates this behavior:
这是一个非常简单的测试,演示了这种行为:
import React, { useState, useEffect } from 'react';
import { mount } from 'enzyme';
const SimpleComponent = () => {
const [data, setData] = useState('initial');
useEffect(() => {
setImmediate(() => setData('updated'));
}, []);
return (<div>{data}</div>);
};
test('SimpleComponent', done => {
const wrapper = mount(<SimpleComponent/>);
setImmediate(done);
});
As I was searching for more info I stumbled on enzyme
issue #2073opened just 10 hours ago talking about this same behavior.
当我在搜索更多信息时,我偶然发现了10 小时前刚刚打开的enzyme
问题 #2073,讨论了同样的行为。
I added the above test in a commentto help the enzyme
devs address the issue.
我在评论中添加了上述测试以帮助enzyme
开发人员解决该问题。
回答by ash
I tried the solution listed above:
我尝试了上面列出的解决方案:
it('should render a proper table data', done => {
const mock = new MockAdapter(axios);
mock.onGet('/path/to/api').reply(200, response.data);
const component = mount(<SalesPlanTable />);
setImmediate(() => {
component.update();
console.log(component.debug());
done();
});
});
I have my expect
line after setImmediate
我有我的expect
线路setImmediate
expect(component.html()).toContain("my text");
I find that console.log(component.debug())
in setImmediate
has the correct text. But component.html()
in my expectation doesn't, and my expectation fails.
我发现console.log(component.debug())
insetImmediate
有正确的文字。但component.html()
在我的期望中没有,我的期望失败了。
Why is this?
为什么是这样?
Edit:
编辑:
I was able to fix my problem by using beforeEach though I don't understand why.
虽然我不明白为什么,但我能够通过使用 beforeEach 来解决我的问题。
describe('Hello', () => {
beforeEach(() => {
const mockAxios = new MockAdapter(axios);
const mockData : HelloData = { Message: "mock Hello, world"};
mockAxios.onGet(helloUrl).reply(200, mockData);
component = mount(<HelloAxios />);
})
it(' using axios should contain "mock Hello, world"', () => {
expect(component.html()).toContain("mock Hello, world");
});
});