这是我第一次在 react.-native 中使用 Redux Hooks,但我在测试中遇到了麻烦,因为当我运行测试时,我收到以下警告:
警告:React 检测到 调用的 Hook 顺序发生了变化 保修主页按钮。如果不修复,这将导致错误和错误。 有关更多信息,请阅读钩子规则:
Previous render Next render ------------------------------------------------------ 1. useState useContext ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
我不确定问题是我的代码还是我测试的方式,应用程序运行良好。如果我独立运行每个测试,测试通过而不发出警告,如果我运行所有三个测试,最后一个测试会抛出警告和错误。
如果你们能指出我犯错误的正确方向,我将不胜感激。
保修主页按钮.js
import React, { useState, useEffect, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { WarrantiesHomeMenu } from '../WarrantiesHomeMenu';
import { userService } from '../services';
import { HomeButton } from '../components';
import { screenNames } from '../constants';
import { userActions } from '../store';
import PropTypes from 'prop-types';
const WarrantiesHomeButton = ({ navigation }) => {
const [showWarrantiesMenu, setShowWarrantiesMenu] = useState(false);
const { warrantiesLoginFlow, user } = useSelector(
(state) => state.userReducer,
);
const dispatch = useDispatch();
const handlePressWarranties = () => {
if (!userService.isAuthenticated()) {
dispatch(userActions.warrantiesLoginStart());
navigation.navigate(screenNames.SIGN_UP);
} else {
setShowWarrantiesMenu(true);
}
};
useEffect(() => {
validateWarrantiesFlow();
});
const validateWarrantiesFlow = useCallback(() => {
if (warrantiesLoginFlow && user.id) {
setShowWarrantiesMenu(true);
dispatch(userActions.warrantiesLoginStop());
}
}, [warrantiesLoginFlow, user.id, dispatch]);
const handleModalPress = () => {
setShowWarrantiesMenu(false);
navigation.navigate(screenNames.SERVICE_CENTERS);
};
const closeWarrantiesHomeMenu = () => {
setShowWarrantiesMenu(false);
};
return (
<>
<HomeButton
testID="HomeButton"
text="Revisiones garantía"
icon="toolBox"
onPress={handlePressWarranties}
/>
<WarrantiesHomeMenu
visible={showWarrantiesMenu}
onPress={handleModalPress}
navigation={navigation}
closeModal={closeWarrantiesHomeMenu}
/>
</>
);
};
WarrantiesHomeButton.propTypes = {
navigation: PropTypes.objectOf(PropTypes.any),
};
export { WarrantiesHomeButton };
保修主页按钮测试.js
import * as React from 'react';
import { Provider } from 'react-redux';
import {
render as rtlRender,
fireEvent,
wait,
act,
} from '@testing-library/react-native';
import { userService } from '../services';
import { screenNames } from '../constants';
import { WarrantiesHomeButton } from './WarrantiesHomeButton';
import { store } from '@auteco/store';
jest.mock('@auteco/components', () => require('@auteco/test').mockComponents());
jest.mock('../WarrantiesHomeMenu', () => {
const { mockComponent } = require('@auteco/test');
return {
WarrantiesHomeMenu: mockComponent('WarrantiesHomeMenu'),
};
});
describe('<WarantiesHomeButton/>', () => {
let mockProps;
const setState = jest.fn();
beforeAll(() => {
mockProps = {
navigation: {
navigate: jest.fn(),
dangerouslyGetParent: jest.fn(() => ({ state: null })),
},
};
jest.spyOn(userService, 'isAuthenticated').mockReturnValue(true);
});
function render(ui) {
return rtlRender(<Provider store={store}>{ui}</Provider>);
}
it('Should Render correctly', () => {
const { baseElement } = render(<WarrantiesHomeButton {...mockProps} />);
expect(baseElement).toMatchSnapshot();
});
describe('And user is authenticated', () => {
describe('And button is pressed', () => {
it('Should show WarrantiesMenu by setting setShowWarrantiesMenu state to true', async () => {
jest.spyOn(React, 'useState').mockReturnValue([false, setState]);
const { getByTestId } = render(<WarrantiesHomeButton {...mockProps} />);
const button = getByTestId('HomeButton');
button.props.onPress();
await wait();
expect(setState).toBeCalledWith(true);
});
});
});
describe('And user is not authenticated', () => {
describe('And button is pressed', () => {
it('Should navigate to signUpScreen', async () => {
jest.spyOn(userService, 'isAuthenticated').mockReturnValue(false);
jest.spyOn(React, 'useState').mockReturnValue([false, setState]);
const { getByTestId } = render(<WarrantiesHomeButton {...mockProps} />);
const button = getByTestId('HomeButton');
act(() => {
button.props.onPress();
});
expect(mockProps.navigation.navigate).toBeCalledWith(
screenNames.SIGN_UP,
);
});
});
});
});
控制台中的错误
console.error node_modules/react-native/Libraries/YellowBox/YellowBox.js:63
Warning: React has detected a change in the order of Hooks called by WarrantiesHomeButton. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks
Previous render Next render
------------------------------------------------------
1. useState useContext
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
in WarrantiesHomeButton
in Provider
in View (created by View)
in View (created by AppContainer)
in View (created by View)
in View (created by AppContainer)
in AppContainer (at src/index.js:26)
console.error node_modules/react-native/Libraries/YellowBox/YellowBox.js:63
The above error occurred in the <WarrantiesHomeButton> component:
in WarrantiesHomeButton
in Provider
in View (created by View)
in View (created by AppContainer)
in View (created by View)
in View (created by AppContainer)
in AppContainer (at src/index.js:26)
Consider adding an error boundary to your tree to customize error handling behavior.
FAIL src/components/home/WarrantiesHomeButton/WarrantiesHomeButton.test.js
<WarantiesHomeButton/>
✓ Should Render correctly (55ms)
And user is authenticated
And button is pressed
✓ Should show WarrantiesMenu by setting setShowWarrantiesMenu state to true (12ms)
And user is not authenticated
And button is pressed
✕ Should navigate to signUpScreen (77ms)
● <WarantiesHomeButton/> › And user is not authenticated › And button is pressed › Should navigate to signUpScreen
TypeError: Cannot read property 'length' of undefined
11 | const [showWarrantiesMenu, setShowWarrantiesMenu] = useState(false);
12 |
> 13 | const { warrantiesLoginFlow, user } = useSelector(
| ^
14 | (state) => state.userReducer,
15 | );
16 | const dispatch = useDispatch();
at areHookInputsEqual (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:5703:38)
at updateMemo (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6336:11)
at Object.useMemo (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6703:16)
at useMemo (node_modules/react/cjs/react.development.js:1592:21)
at useSelectorWithStoreAndSubscription (node_modules/react-redux/lib/hooks/useSelector.js:31:41)
at useSelector (node_modules/react-redux/lib/hooks/useSelector.js:117:12)
at WarrantiesHomeButton (src/components/home/WarrantiesHomeButton/WarrantiesHomeButton.js:13:41)
at renderWithHooks (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:5762:18)
at updateFunctionComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7579:20)
at beginWork$1 (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:9152:16)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 2 passed, 3 total
Snapshots: 1 passed, 1 total
Time: 4.846s, estimated 5s
Ran all test suites matching /WarrantiesHomeButton/i.
Watch Usage: Press w to show more.
Previous render Next render
------------------------------------------------------
1. useState useState
2. useState useState
3. useState useState
4. useState useState
5. useContext useEffect
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
正如您在上面观察到的那样,我遇到了类似的问题。我最终通过将每个组件的创建/渲染包装在act
的it
块中来解决它。似乎测试中useEffect
对组件的异步操作相互冲突。我按以下方式解决:
let component: any;
await act(async () => {
component = create(
<MockedProvider>
<SelectAccountScreen {...props} />
</MockedProvider>,
);
});
PS:我正在使用react-test-renderer
(如果您想知道create
来自哪里(