React-native-reanimated: jest support: ReanimatedEventEmitter throws in a global scope when touched with jest

Created on 8 Mar 2019  路  11Comments  路  Source: software-mansion/react-native-reanimated

The following line of code is dangerous in a jest environment:
https://github.com/kmagiera/react-native-reanimated/blob/3f9f5cca3bd7867cf9e17147f5e9200567f0cdda/src/ReanimatedEventEmitter.js#L4

Scenario:

I upgraded react-navigation-tabs to a version that started including this library.

Immediately any of my unit tests that happened to import my navigation stack triggered crashes even though I wasn't actually using react-navigation-tabs in my tests.


Callstack

      at invariant (node_modules/react-native/node_modules/fbjs/lib/invariant.js:40:15)
      at new NativeEventEmitter (node_modules/react-native/Libraries/EventEmitter/NativeEventEmitter.js:36:36)
      at Object.<anonymous> (node_modules/react-native-reanimated/src/ReanimatedEventEmitter.js:4:1)
      at Object.<anonymous> (node_modules/react-native-reanimated/src/core/AnimatedCall.js:1:824)
      at Object.<anonymous> (node_modules/react-native-reanimated/src/base.js:8:44)
      at Object.<anonymous> (node_modules/react-native-reanimated/src/Easing.js:1:411)
      at Object.<anonymous> (node_modules/react-native-reanimated/src/Animated.js:2:38)
      at Object.<anonymous> (node_modules/react-navigation-tabs/src/views/BottomTabBar.js:11:53)

In a jest environment, obviously NativeModules has nothing in it.

Proposal

If we change the EventEmitter to lazily create on first access, this would be safer, possibly?

export default {
  get emitter() {
    delete this.emitter;
    return this.emitter = new NativeEventEmitter(ReanimatedModule);
  }
}

Thoughts?

鈴璹uick-fix

Most helpful comment

I feel foolish for not figuring this out before now, but the following lines of code in my beforeAll.js setup script were all that was required (as of the current version of the library:

jest.mock("react-native-reanimated", () =>
    jest.requireActual("../../node_modules/react-native-reanimated/mock"),
);

Maybe we should add this line to the README or docs somewhere?

All 11 comments

I would love any advice you could provide to workaround this issue!

This would also require fixes to avoid global-scope touching of the NativeModule here:
https://github.com/kmagiera/react-native-reanimated/blob/3f9f5cca3bd7867cf9e17147f5e9200567f0cdda/src/ConfigHelper.js#L106-L126

I have similar issue, I am using rn-gesture-handler & reanimated.
And when running Jest test, I get error:

Test suite failed to run

    Invariant Violation: Native module cannot be null.

      at invariant (node_modules/react-native/node_modules/fbjs/lib/invariant.js:40:15)
      at new NativeEventEmitter (node_modules/react-native/Libraries/EventEmitter/NativeEventEmitter.js:36:36)
      at Object.<anonymous> (node_modules/react-native-reanimated/src/ReanimatedEventEmitter.js:4:1)
      at Object.<anonymous> (node_modules/react-native-reanimated/src/core/AnimatedCall.js:1:181)

I have successfully mocked rn-gesture-handler with just:

jest.mock("react-native-gesture-handler", () => {});

But I cannot figure out how to mock reanimated properly.

I have tried to mock it like this:

jest.mock("react-native-reanimated", () => {
  return {
    Value: jest.fn(() => 0),
    event: jest.fn(),
    add: jest.fn(() => 0),
    eq: jest.fn(() => true),
    set: jest.fn(() => 0),
    cond: jest.fn(),
    interpolate: jest.fn(() => {}),
    Extrapolate: { CLAMP: jest.fn() }
  };
});

But then I get error saying:

```
Invariant Violation: Element 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 fro
m the file it's defined in, or you might have mixed up default and named imports.

Check the render method of `BottomDrawer`.

  at invariant (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:55:19)
  at createFiberFromTypeAndProps (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:2015:15)
  at createFiberFromElement (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:2036:19)
  at reconcileSingleElement (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:5980:27)
  at reconcileChildFibers (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6037:39)
  at reconcileChildren (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6404:32)
  at updateHostComponent (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:6749:7)
  at beginWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:7413:18)
  at performUnitOfWork (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:10149:16)
  at workLoop (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:10181:28

)
```

I have managed to mock it successfully 馃榿
The problem with mock from above is that I forgot to mock View. Working mock looks like:

import { View } from "react-native";

jest.mock("react-native-reanimated", () => {
  return {
    Value: jest.fn(),
    event: jest.fn(),
    add: jest.fn(),
    eq: jest.fn(),
    set: jest.fn(),
    cond: jest.fn(),
    interpolate: jest.fn(),
    View: View,
    Extrapolate: { CLAMP: jest.fn() }
  };
});

I need to mock Transition for `react-navigation-animated-switch' so my version like this:

jest.mock('react-native-reanimated', () => {
  const View = require('react-native').View;

  return {
    Value: jest.fn(),
    event: jest.fn(),
    add: jest.fn(),
    eq: jest.fn(),
    set: jest.fn(),
    cond: jest.fn(),
    interpolate: jest.fn(),
    View: View,
    Extrapolate: { CLAMP: jest.fn() },
    Transition: {
      Together: 'Together',
      Out: 'Out',
      In: 'In',
    },
  };
});

Hi @mralj and @EricMcRay I get the following error after using your mock solution:

TypeError: Cannot read property 'out' of undefined

Is this part of <Transition.Out> component?
Just for test I have added <Transition.Out> to my render function and got same error as you, but @EricMcRay solution worked for me (ie appending this to my code helped):

Transition: {
      Out: 'Out',
    },

So in my case whole thing now looks like:

jest.mock("react-native-reanimated", () => {
  return {
    Value: jest.fn(),
    event: jest.fn(),
    add: jest.fn(),
    eq: jest.fn(),
    set: jest.fn(),
    cond: jest.fn(),
    interpolate: jest.fn(),
    View: mockView,
    Extrapolate: { CLAMP: jest.fn() },
    Clock: jest.fn(),
    greaterThan: jest.fn(),
    lessThan: jest.fn(),
    startClock: jest.fn(),
    stopClock: jest.fn(),
    clockRunning: jest.fn(),
    not: jest.fn(),
    or: jest.fn(),
    and: jest.fn(),
    spring: jest.fn(),
    decay: jest.fn(),
    defined: jest.fn(),
    call: jest.fn(),
    Code: mockView,
    block: jest.fn(),
    abs: jest.fn(),
    greaterOrEq: jest.fn(),
    lessOrEq: jest.fn(),
    debug: jest.fn(),
    Transition: {
      Out: "Out"
    }
  };
});

Cool. Thanks @mralj. I had it resolved by using react-navigation/tabs#128. There you'll find an indepth solution by @Satya164 at #276

If anyone has problems with Jest and Transitioning.View, I have solved it this way

import { View as mockView } from "react-native";
const React = require("React");

class MockedTransitioningView extends React.Component {
  animateNextTransition() {}
  render() {
    return <mockView {...this.props}>{this.props.children}</mockView>;
  }
}

and then:

jest.mock("react-native-reanimated", () => {
  return {    
   ...
    Transitioning: {
      View: MockedTransitioningView
    }
  };
});

It would be great if the necessary mocking was either automatically hooked up for Jest, or at least if there was a source-file or function we could execute to hook it up.

I was originally able to solve this by mocking NativeEventEmitter, but today we're upgrading to react-native 0.62, and I had to re-examine this ticket & thread.

For me, it looks like we needed @mralj's answer too: https://github.com/software-mansion/react-native-reanimated/issues/205#issuecomment-492689486

I feel foolish for not figuring this out before now, but the following lines of code in my beforeAll.js setup script were all that was required (as of the current version of the library:

jest.mock("react-native-reanimated", () =>
    jest.requireActual("../../node_modules/react-native-reanimated/mock"),
);

Maybe we should add this line to the README or docs somewhere?

Was this page helpful?
0 / 5 - 0 ratings