我的React应用程序具有一个组件,该组件从远程服务器获取要显示的数据。在预钩时代,==1040== 624 bytes in 1 blocks are possibly lost in loss record 15 of 15
==1040== at 0x4C2B955: calloc (vg_replace_malloc.c:711)
==1040== by 0x40126B4: _dl_allocate_tls (in /usr/lib64/ld-2.17.so)
==1040== by 0x50B97AB: pthread_create@@GLIBC_2.2.5 (in /usr/lib64/libpthread-2.17.so)
==1040== by 0xA7381C9: boost::thread::start_thread_noexcept() (in boostLocation/lib/libboost_thread.so.1.67.0)
==1040== by 0x56336CA: start_thread (thread.hpp:179)
==1040== by 0x56336CA: boost::thread::thread<void (*)(<arguments>) (thread.hpp:435)
==1040== by 0x5630FA8: <my code, allocating then freeing a new boost::thread>
是一个理想的选择。但是现在我想为此使用钩子。
componentDidMount()
我使用Jest和Enzyme进行的测试如下:
const App = () => {
const [ state, setState ] = useState(0);
useEffect(() => {
fetchData().then(setState);
});
return (
<div>... data display ...</div>
);
};
测试成功,但是会记录一些警告:
import React from 'react';
import { mount } from 'enzyme';
import App from './App';
import { act } from 'react-test-renderer';
jest.mock('./api');
import { fetchData } from './api';
describe('<App />', () => {
it('renders without crashing', (done) => {
fetchData.mockImplementation(() => {
return Promise.resolve(42);
});
act(() => mount(<App />));
setTimeout(() => {
// expectations here
done();
}, 500);
});
});
App组件的唯一更新发生在Promise回调中。如何确保在console.error node_modules/react-dom/cjs/react-dom.development.js:506
Warning: An update to App inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at (redacted)
in App (created by WrapperComponent)
in WrapperComponent
块内 中发生这种情况?文档明确建议断言发生在act
块的外部处。此外,将它们放在里面不会改变警告。
答案 0 :(得分:10)
该问题是由Component内部的许多更新引起的。
我遇到了同样的问题,这可以解决问题。
await act( async () => mount(<App />));
答案 1 :(得分:1)
酶不支持钩子,因为它是一个相对较新的功能: https://github.com/airbnb/enzyme/issues/2011
也许您可以同时使用纯Jest? 也不必担心警告,它应该在发布React 16.9.0时消失(请参见此拉取请求https://github.com/facebook/react/pull/14853)
答案 2 :(得分:0)
我也遇到了同样的问题,最后写了一个库,通过模拟所有标准React Hooks来解决这个问题。
基本上,act()
是同步函数,类似于useEffect
,但是useEffect
执行异步函数。无法使act()能够“等待”执行。开火,算了!
此处的文章:https://medium.com/@jantoine/another-take-on-testing-custom-react-hooks-4461458935d4
此处的图书馆:https://github.com/antoinejaussoin/jooks
要测试代码,您首先需要将逻辑(提取等)提取到单独的自定义钩子中:
const useFetchData = () => {
const [ state, setState ] = useState(0);
useEffect(() => {
fetchData().then(setState);
});
return state;
}
然后,使用Jooks,您的测试应如下所示:
import init from 'jooks';
[...]
describe('Testing my hook', () => {
const jooks = init(() => useFetchData());
// Mock your API call here, by returning 'some mocked value';
it('Should first return 0', () => {
const data = jooks.run();
expect(data).toBe(0);
});
it('Then should fetch the data and return it', async () => {
await jooks.mount(); // Fire useEffect etc.
const data = jooks.run();
expect(data).toBe('some mocked value');
});
});
答案 3 :(得分:0)
我已经创建了用于测试异步钩子的示例。
https://github.com/oshri6688/react-async-hooks-testing
CommentWithHooks.js
:
import { getData } from "services/dataService";
const CommentWithHooks = () => {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const fetchData = () => {
setIsLoading(true);
getData()
.then(data => {
setData(data);
})
.catch(err => {
setData("No Data");
})
.finally(() => {
setIsLoading(false);
});
};
useEffect(() => {
fetchData();
}, []);
return (
<div>
{isLoading ? (
<span data-test-id="loading">Loading...</span>
) : (
<span data-test-id="data">{data}</span>
)}
<button
style={{ marginLeft: "20px" }}
data-test-id="btn-refetch"
onClick={fetchData}
>
refetch data
</button>
</div>
);
};
CommentWithHooks.test.js
:
import React from "react";
import { mount } from "enzyme";
import { act } from "react-dom/test-utils";
import MockPromise from "testUtils/MockPromise";
import CommentWithHooks from "./CommentWithHooks";
import { getData } from "services/dataService";
jest.mock("services/dataService", () => ({
getData: jest.fn(),
}));
let getDataPromise;
getData.mockImplementation(() => {
getDataPromise = new MockPromise();
return getDataPromise;
});
describe("CommentWithHooks", () => {
beforeEach(() => {
jest.clearAllMocks();
});
it("when fetching data successed", async () => {
const wrapper = mount(<CommentWithHooks />);
const button = wrapper.find('[data-test-id="btn-refetch"]');
let loadingNode = wrapper.find('[data-test-id="loading"]');
let dataNode = wrapper.find('[data-test-id="data"]');
const data = "test Data";
expect(loadingNode).toHaveLength(1);
expect(loadingNode.text()).toBe("Loading...");
expect(dataNode).toHaveLength(0);
expect(button).toHaveLength(1);
expect(button.prop("onClick")).toBeInstanceOf(Function);
await getDataPromise.resolve(data);
wrapper.update();
loadingNode = wrapper.find('[data-test-id="loading"]');
dataNode = wrapper.find('[data-test-id="data"]');
expect(loadingNode).toHaveLength(0);
expect(dataNode).toHaveLength(1);
expect(dataNode.text()).toBe(data);
});
testUtils/MockPromise.js
:
import { act } from "react-dom/test-utils";
const createMockCallback = callback => (...args) => {
let result;
if (!callback) {
return;
}
act(() => {
result = callback(...args);
});
return result;
};
export default class MockPromise {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.promiseResolve = resolve;
this.promiseReject = reject;
});
}
resolve(...args) {
this.promiseResolve(...args);
return this;
}
reject(...args) {
this.promiseReject(...args);
return this;
}
then(...callbacks) {
const mockCallbacks = callbacks.map(callback =>
createMockCallback(callback)
);
this.promise = this.promise.then(...mockCallbacks);
return this;
}
catch(callback) {
const mockCallback = createMockCallback(callback);
this.promise = this.promise.catch(mockCallback);
return this;
}
finally(callback) {
const mockCallback = createMockCallback(callback);
this.promise = this.promise.finally(mockCallback);
return this;
}
}
答案 4 :(得分:0)
我通过以下步骤解决了此问题
在设置文件中导入再生器运行时。
import "regenerator-runtime/runtime";
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({
adapter: new Adapter()
});
自动换行和其他可能导致状态更改的动作。从简单的react-dom / test-utils,async和await导入行为,如下所示。
import React from 'react';
import { mount } from 'enzyme';
import App from './App';
import { act } from "react-dom/test-utils";
jest.mock('./api');
import { fetchData } from './api';
describe('<App />', () => {
it('renders without crashing', async (done) => {
fetchData.mockImplementation(() => {
return Promise.resolve(42);
});
await act(() => mount(<App />));
setTimeout(() => {
// expectations here
done();
}, 500);
});
});
希望这会有所帮助。