Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jest support: ReanimatedEventEmitter throws in a global scope when touched with jest #205

Closed
fbartho opened this issue Mar 7, 2019 · 11 comments

Comments

@fbartho
Copy link

fbartho commented Mar 7, 2019

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?

@fbartho
Copy link
Author

fbartho commented Mar 8, 2019

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

@fbartho
Copy link
Author

fbartho commented Mar 8, 2019

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

@mralj
Copy link

mralj commented Apr 11, 2019

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
)

@mralj
Copy link

mralj commented Apr 11, 2019

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() }
  };
});

@EricMcRay
Copy link

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',
    },
  };
});

@nwaughachukwuma
Copy link

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

TypeError: Cannot read property 'out' of undefined

@mralj
Copy link

mralj commented May 15, 2019

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"
    }
  };
});

@nwaughachukwuma
Copy link

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

@mralj
Copy link

mralj commented Jun 11, 2019

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
    }
  };
});

@fbartho
Copy link
Author

fbartho commented Mar 7, 2020

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: #205 (comment)

@fbartho
Copy link
Author

fbartho commented Mar 7, 2020

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants