据我了解,应该这样做,以便 useFocusEffect 可以作为 useEffect 进行测试(模拟)。我使用 useFocusEffect 来获取数据:
useFocusEffect(
useCallback(() => {
fetchData();
}, [fetchData]),
);
错误信息: 反应导航挂钩需要导航上下文,但无法找到。确保您没有忘记创建和渲染反应导航应用程序容器。如果需要访问可选的导航对象,可以使用Context(NavigationContext),它可能会返回
套餐版本:
"jest": "^24.9.0",
"react-native": "0.61.2",
"react-navigation": "^4.0.10",
"react-navigation-hooks": "^1.1.0",
"@testing-library/react-native": "^4.0.14",
假设您在测试中渲染组件,则需要将其包装在假的
<NavigationContext>
中。这样做可以让 useFocusEffect
查找所需的内容,以确定该组件是否已被应用程序的导航聚焦。
此示例使用
render
中的 react-native-testing-library
。我认为它与其他渲染方法类似。
import { NavigationContext } from "@react-navigation/native"
import { render } from "react-native-testing-library"
// fake NavigationContext value data
const navContext = {
isFocused: () => true,
// addListener returns an unscubscribe function.
addListener: jest.fn(() => jest.fn())
}
// MyComponent needs to be inside an NavigationContext, to allow useFocusEffect to function.
const { toJSON } = render(
<NavigationContext.Provider value={navContext}>
<MyComponent />
</NavigationContext.Provider>
)
这只是@meshantz 的上述答案的完整版本。
import { NavigationContext } from '@react-navigation/native';
import { render } from '@testing-library/react-native';
import React from 'react';
// This would probably be imported from elsewhere...
const ComponentUnderTest = () => {
useFocusEffect(
useCallback(() => {
fetchData();
}, [fetchData]),
);
return null;
};
const mockFetchData = jest.fn();
jest.mock('fetchData', () => mockFetchData);
describe('testing useFocusOnEffect in ComponentUnderTest', () => {
afterAll(() => {
jest.restoreAllMocks();
});
describe('when the view comes into focus', () => {
it('calls fetchData', () => {
const navContextValue = {
isFocused: () => false,
addListener: jest.fn(() => jest.fn()),
};
render(
<NavigationContext.Provider value={navContextValue}>
<ComponentUnderTest />
</NavigationContext.Provider>,
);
expect(mockFetchData).toBeCalledTimes(0);
render(
<NavigationContext.Provider
value={{
...navContextValue,
isFocused: () => true,
}}
>
<ComponentUnderTest />
</NavigationContext.Provider>,
);
expect(mockFetchData).toBeCalledTimes(1);
});
});
});
对于 TypeScript,也需要满足类型要求,所以在我的例子中,它是通过使用
jest.requireActual
: 来完成的
const withProvider = (element, store = defaultStore) => {
// fake NavigationContext value data
const actualNav = jest.requireActual("@react-navigation/native");
const navContext = {
...actualNav.navigation,
navigate: () => {},
dangerouslyGetState: () => {},
setOptions: () => {},
addListener: () => () => {},
isFocused: () => true,
};
return (
<NavigationContext.Provider value={navContext}>
<MyComponent />
</NavigationContext.Provider>
);
};
it("renders correctly", () => {
render(withProvider(() => <SportsBooksScreen {...defaultProps} />));
});
我在这个线程中提出的解决方案存在问题/限制,所以我最终用“React.useEffect”嘲笑“useFocusEffect”。
它做得很好:我的测试现在是绿色的!
jest.mock('@react-navigation/native', () => {
const { useEffect } = require('react');
const actualModule = jest.requireActual('@react-navigation/native');
return {
...actualModule,
useFocusEffect: useEffect,
};
});
不要使用 useFocusEffect,而是使用 useIsFocused 和 useEffect,并且代码可以正常工作。
In Your component:
import React, { useEffect } from 'react';
import { useIsFocused } from '@react-navigation/native';
const Component = () => {
const isFocused = useIsFocused();
useEffect(() => {
if (isFocused) {
fetchData();
}
}, [isFocused]);
return (<><View testID="child_test_id">{'render child nodes'}</View></>)
}
For Testing:
import Component from '--path-to-component--';
jest.mock('--path-to-fetchData--');
jest.mock('@react-navigation/native', () => {
return {
useIsFocused: () => true
};
});
it('should render child component when available', async () => {
const mockedData = [];
fetchData.mockImplementation(() => mockedData);
let screen = null;
await act(async () => {
screen = renderer.create(<Component />);
});
const childNode = screen.root.findByProps({ testID: 'child_test_id' });
expect(childNode.children).toHaveLength(1);
});
useFocusEffect
使用 navigation.isFocused()
,可从 jest 的 renderHookWithProviders
访问。
做一个
navigation.isFocused.mockReturnValue(true);
应该可以解决问题!现在刚刚尝试了一下,效果很好。
创建组件 FocusEffect
import { useFocusEffect } from "@react-navigation/native";
import { BackHandler } from "react-native";
import React from "react";
export default function FocusEffect({ onFocus, onFocusRemoved }) {
useFocusEffect(
React.useCallback(() => {
onFocus();
return () => onFocusRemoved();
}, [onFocus, onFocusRemoved]),
);
return null;
}
使用示例:
import React from 'react';
import { Text, View } from 'react-native';
import { FocusEffect } from './components';
const App = () => {
onFocus = () => {
// ============>>>> onFocus <<<<==============
fetchData();
};
onFocusRemoved = () => {
// ============>>>> onFocusRemoved <<<<==============
};
return (
<View>
<FocusEffect
onFocus={this.onFocus}
onFocusRemoved={this.onFocusRemoved}
/>
<Text>Hello, world!</Text>
</View>
)
}
export default App;
如果
useFocusEffect()
中的代码对您的测试并不重要,您可以按如下方式模拟该钩子:
jest.mock("@react-navigation/native", () => ({
useFocusEffect: jest.fn(),
// ...
}));