react-native で開発する際、ページ遷移を実装するのに最も適しているライブラリは react-navigation であろう(2020/06/13時点)。
react-navigation v5 では、navigation を行うために便利な useNavigation
や useRoute
などの custom hooks が提供されている。
useNavigation
は、 <NavigationContainer>
の中で存在する Component の Context から navigation
を取ってきて使用可能にするための custom hooks である。
これらの hooks はとても便利である反面、component を持たない custom hooks の中で使う場合、@testing-library/react-hooks
を使って custom hooks のテストする時に wrapper に navigation の context を渡す必要がある。
useNavigation
や useRoute
の返り値自体を mock しても良いが、params が想定通りに渡っているかどうかを確認したい場合、返り値を mock すると不便だったりする。
解決策
context を渡す一番てっとり速い方法は context を含んでいる provider でラップすることである。
なので、今回以下のようなラッパーを作成した。
import * as React from 'react';
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';
import {View} from 'react-native';
const Stack = createStackNavigator();
export const MockNavigation: React.FC = ({children}) => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="MockScreen"
component={() => <View>{children}</View>}
/>
</Stack.Navigator>
</NavigationContainer>
);
};
@testing-library/react-hooks での使い方
以下の様なcustomHooks があるとする
import {useNavigation} from '@react-navigation/native';
export const useCustomHooks = () => {
const navigation = useNavigation()
/**
* navigation を含んだ処理
*/
}
以下のようにテストを書ける。
custom hooks の中で、useNavigation を使っているため、renderHook の第二引数として wrapper を提供して、上で作成した MockNavigation を使うことでテストしたい customHooks の中にも context が入る。
import {renderHook} from '@testing-library/react-hooks';
import {MockProvider} from '__mocks__/MockProvider';
import {useCustomHooks} from '__hooks__/useCustomHooks'
describe('useCustomHooks', ()=> {
it('works',()=>{
const {result} = renderHook(useCustomHooks, {
wrapper: {children} => <MockNavigation>{children}</MockNavigation>
})
const expected = { foo: 'bar' }
expect(result.current).toMatchObject(expected)
})
})
注意点
上記例では react-navigation の mock は行わなかったが、react-navigation は内部で react-native-gesture-handler と react-native-reanimated というNative層を触るライブラリを使用しているため、それらの mock は行わないと jest が落ちてしまう。
セットアップは以下のように行えば良い。
jest のセットアップ
各テストで必要なファイルをmockするために、general.js
作成する。
今回は react-native-gesture-handler
及び react-native-reanimated
の mock を作成する。
module.exports = {
preset: 'react-native',
setupFiles: ['<rootDir>/__setup__/general.js'],
transformIgnorePatterns: [
'node_modules/(?!(jest-)?react-native|react-navigation|react-navigation-redux-helpers|@react-navigation|@react-native-community/.*)',
],
};
/* eslint-env jest */
jest.mock('react-native-gesture-handler', () => {
const View = require('react-native/Libraries/Components/View/View');
return {
Swipeable: View,
DrawerLayout: View,
State: {},
ScrollView: View,
Slider: View,
Switch: View,
TextInput: View,
ToolbarAndroid: View,
ViewPagerAndroid: View,
DrawerLayoutAndroid: View,
WebView: View,
NativeViewGestureHandler: View,
TapGestureHandler: View,
FlingGestureHandler: View,
ForceTouchGestureHandler: View,
LongPressGestureHandler: View,
PanGestureHandler: View,
PinchGestureHandler: View,
RotationGestureHandler: View,
/* Buttons */
RawButton: View,
BaseButton: View,
RectButton: View,
BorderlessButton: View,
/* Other */
FlatList: View,
gestureHandlerRootHOC: jest.fn(),
Directions: {},
};
});
jest.mock('react-native-reanimated', () =>
require('react-native-reanimated/mock'),
);
まとめ
- useNavigation を含んだ custom hooks のテストには、Navigation のラッパーが必要になる。
- また、jest のセットアップ時に native層を触る周辺ライブラリの mock をする必要がある。
参考