react-native-reanimated
react-native-reanimated copied to clipboard
Animated.createAnimatedComponent mock returns undefined instead of the Component when running jest
Description
I have a screen that contains an animated FlatList and I need to use ref for it, so I created my Animated list like below:
const AnimatedList = Animated.createAnimatedComponent(FlatList)
I need to test an element in this list is working fine, however, I cannot get the test running. Because it fails on my AnimatedList component with the following error:
console.error
Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
I have mocked the react-native-reanimated component like this:
jest.mock('react-native-reanimated', () => {
return {
...jest.requireMock('react-native-reanimated/mock'),
useSharedValue: jest.fn,
useAnimatedStyle: jest.fn,
};
});
So I tried to log what is really inside my AnimatedList and got undefined.
I tried to log Animated.createAnimatedComponent itself since I knew it exists in the package mock, and this is what is logged in the console:
[Function: createAnimatedComponent] {
_isMockFunction: true,
getMockImplementation: [Function (anonymous)],
mock: [Getter/Setter],
mockClear: [Function (anonymous)],
mockReset: [Function (anonymous)],
mockRestore: [Function (anonymous)],
mockReturnValueOnce: [Function (anonymous)],
mockResolvedValueOnce: [Function (anonymous)],
mockRejectedValueOnce: [Function (anonymous)],
mockReturnValue: [Function (anonymous)],
mockResolvedValue: [Function (anonymous)],
mockRejectedValue: [Function (anonymous)],
mockImplementationOnce: [Function (anonymous)],
mockImplementation: [Function (anonymous)],
mockReturnThis: [Function (anonymous)],
mockName: [Function (anonymous)],
getMockName: [Function (anonymous)]
}
But when I log Animated.createAnimatedComponent(FlatList) it returns undefined.
I tried to mock this function after the line ...jest.requireMock('react-native-reanimated/mock'), like (Component) => Component but it didn't work.
So I tried to remove this line ...jest.requireMock('react-native-reanimated/mock'), and try to mock the required components and functions myself. This time it worked and in the logs, I could see that the component is returned by the Animated.createAnimatedComponent(FlatList), however, the test failed again, because other reanimated components were not mocked correctly. So I believe the issue should be inside the mock file, I couldn't figure out what is the reason it is not returning undefined instead of the component.
I would be grateful if you help me fix this issue, it took me 2 days but has not been resolved yet.
Steps to reproduce
- Clone the provided repo
- Run
npm ito install the dependencies - Run
npx jest(it will fail on AnimatedList component and I provided the logs I described above which you can check on the console)
Snack or a link to a repository
https://github.com/mshavandi/reanimatedMockIssueWithJest
Reanimated version
3.0.0-rc.1
React Native version
0.68.2
Platforms
Android
JavaScript runtime
No response
Workflow
No response
Architecture
No response
Build type
No response
Device
No response
Device model
No response
Acknowledgements
Yes
For those who need a solution to this problem:
I ended up mocking the reanimated package like this:
jest.mock('react-native-reanimated', () => {
return {
...jest.requireMock('react-native-reanimated/mock'),
useSharedValue: jest.fn,
useAnimatedStyle: jest.fn,
useAnimatedRef: () => ({ current: null }),
default: {
...jest.requireMock('react-native-reanimated/mock').default,
createAnimatedComponent: (Component) => Component,
},
};
});
This solves the issue with Animated components, and also some features related to reanimated V2
Update:
Actually, the problem was with the requireMock, it works with requireActual.
Changing the mock to:
jest.mock('react-native-reanimated', () => {
return {
...jest.requireActual('react-native-reanimated/mock'),
...jest.requireActual('react-native-reanimated/src/reanimated2/mock'),
};
});
solves the problem.
For those who need a solution to this problem: I ended up mocking the
reanimatedpackage like this:jest.mock('react-native-reanimated', () => { return { ...jest.requireMock('react-native-reanimated/mock'), useSharedValue: jest.fn, useAnimatedStyle: jest.fn, useAnimatedRef: () => ({ current: null }), default: { ...jest.requireMock('react-native-reanimated/mock').default, createAnimatedComponent: (Component) => Component, }, }; });This solves the issue with Animated components, and also some features related to reanimated V2
Update:
Actually, the problem was with the
requireMock, it works withrequireActual. Changing the mock to:jest.mock('react-native-reanimated', () => { return { ...jest.requireActual('react-native-reanimated/mock'), ...jest.requireActual('react-native-reanimated/src/reanimated2/mock'), }; });solves the problem.
I have the same problem, but this mock doesn't work for me.
Invariant Violation:createAnimatedComponent` does not support stateless functional components; use a class component instead.
46 | }
47 |
> 48 | const AnimatedColumn = Animated.createAnimatedComponent(Column);
| ^
49 | const defaultAnimationDuration = 0;
50 |
51 | const Overlay: ForwardRefRenderFunction<OverlayRef, OverlayProps> = (
at invariant (node_modules/invariant/invariant.js:40:15)
at Object.createAnimatedComponent (node_modules/react-native/Libraries/Animated/src/createAnimatedComponent.js:28:3)`
i`m tryng testing with a styled component library, maybe my error is because styled components doesnt works with animated components, but idk.
@Michelalmeidasilva You can use styled-components using a dumb class component for that:
class ColumnKlass extends React.Component {
render() {
return <Column />; //Styled component
}
}
const AnimatedColumn = Animated.createAnimatedComponent(ColumnKlass);
Btw seems that some mocks are missing for the Layout components (on both reanimated and reanimated2 mocks). You can solve that issue by mocking the missing stuff yourself 😄
jest.mock('react-native-reanimated', () => {
const Reanimated = require('react-native-reanimated/mock');
// The mock for `call` immediately calls the callback which is incorrect
// So we override it with a no-op
Reanimated.default.call = () => {};
const DefaultLayoutAnimation: unknown = {
delay: (_: number) => DefaultLayoutAnimation,
duration: (_: number) => DefaultLayoutAnimation,
withCallback: () => DefaultLayoutAnimation,
withInitialValues: (_: unknown) => DefaultLayoutAnimation,
randomDelay: (_: number) => DefaultLayoutAnimation,
};
return {
...Reanimated,
FadeIn: DefaultLayoutAnimation,
BounceIn: DefaultLayoutAnimation,
};
});
NOTE: In my case I'm only using FadeIn and BounceIn default animations so I only mocked those two
I am encountering an issue
where I want the withCallback to be called but
even if I remove the line with that Reanimated.default.call
its still not being called
"react-native-reanimated": "^2.14.4",
component
const [openState, setOpenState] = useState(true);
const onWillClose = () => {
setOpenState(false);
};
const onExit = (finished: boolean) => {
'worklet';
console.log('on exit');
if (finished) {
runOnJS(onCloseModal)();
}
};
const onShown = (finished: boolean) => {
'worklet';
console.log('on open');
if (finished && onModalShown) {
runOnJS(onModalShown)();
}
};
return (
<ReAnimated.View {...testProps(testID)} style={styles.main}>
{openState && (
<>
<ReAnimated.View
style={styles.background}
entering={FadeIn}
exiting={FadeOut}
/>
<ReAnimated.View
style={styles.containerStyle}
entering={SlideInDown.withCallback(onShown)}
exiting={SlideOutDown.withCallback(onExit)}
>
<Pressable
{...testProps(testID + '.pressable')}
style={styles.pressable}
onPress={onWillClose}
/>
<View style={styles.contents}>
{children}
<SafeAreaView edges={['bottom']} />
</View>
</ReAnimated.View>
</>
)}
</ReAnimated.View>
);
test
jest.mock('react-native-reanimated', () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const Reanimated = require('react-native-reanimated/mock');
// The mock for `call` immediately calls the callback which is incorrect
// So we override it with a no-op
//Reanimated.default.call = () => {};
const DefaultLayoutAnimation: unknown = {
delay: (_: number) => DefaultLayoutAnimation,
duration: (_: number) => DefaultLayoutAnimation,
withCallback: () => DefaultLayoutAnimation,
withInitialValues: (_: unknown) => DefaultLayoutAnimation,
randomDelay: (_: number) => DefaultLayoutAnimation,
};
return {
...Reanimated,
SlideInDown: DefaultLayoutAnimation,
SlideOutDown: DefaultLayoutAnimation,
};
});
it('invokes open', () => {
render(
<Component
testID="sample"
onCloseModal={mockOnClose}
onModalShown={mockOnOpen}
>
<Text>Sample Text</Text>
<Button title="Sample Button" />
</Component>,
);
expect(mockOnOpen).toHaveBeenCalled();
});
Hey! 👋
The issue doesn't seem to contain a minimal reproduction.
Could you provide a snack or a link to a GitHub repository under your username that reproduces the problem?