React-native: Jest breaking when importing component using state class property

Created on 27 Nov 2018  ·  9Comments  ·  Source: facebook/react-native

Environment

React Native Environment Info:
    System:
      OS: macOS 10.14.1
      CPU: (8) x64 Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
      Memory: 496.23 MB / 16.00 GB
      Shell: 5.3 - /bin/zsh
    Binaries:
      Node: 8.11.2 - /usr/local/bin/node
      Yarn: 1.6.0 - ~/.yarn/bin/yarn
      npm: 5.6.0 - /usr/local/bin/npm
      Watchman: 4.9.0 - /usr/local/bin/watchman
    SDKs:
      iOS SDK:
        Platforms: iOS 12.1, macOS 10.14, tvOS 12.1, watchOS 5.1
      Android SDK:
        API Levels: 21, 22, 23, 24, 25, 26, 27, 28
        Build Tools: 23.0.1, 24.0.1, 25.0.1, 25.0.2, 25.0.3, 26.0.0, 26.0.1, 26.0.2, 27.0.3, 28.0.2, 28.0.3
        System Images: android-22 | Google APIs Intel x86 Atom, android-24 | Google APIs Intel x86 Atom
    IDEs:
      Android Studio: 3.2 AI-181.5540.7.32.5056338
      Xcode: 10.1/10B61 - /usr/bin/xcodebuild
    npmPackages:
      react: ^16.6.3 => 16.6.3
      react-native: ^0.57.5 => 0.57.5
    npmGlobalPackages:
      create-react-native-app: 1.0.0
      react-native-cli: 2.0.1
      react-native: 0.57.5

Additionally I`m using these babel packages:
    "@babel/cli": "^7.1.5",
    "@babel/core": "^7.1.6",
    "@babel/plugin-proposal-class-properties": "^7.1.0",
    "@babel/preset-env": "^7.1.6",
    "@babel/runtime": "^7.1.5",
    "babel-jest": "^23.6.0",
    "babel-plugin-jest-hoist": "^23.2.0",

Description

When I import a component like the example below:

export default class ButtonHold extends React.PureComponent<Props> {
  state = {
    locked: true,
    unlocking: false,
    buttonWidth: 0
  }

....

}

the test produces the following error:

 FAIL  src/components/button-hold.test.js
  <ButtonHold>
    Structure
      ✕ renders correctly (6ms)

  ● <ButtonHold> › Structure › renders correctly

    TypeError: Cannot read property 'default' of undefined

      14 | }
      15 |
    > 16 | export default class ButtonHold extends React.PureComponent<Props> {
         |                                                                                                                                                                                   ^
      17 |   static defaultProps = {
      18 |     style: undefined,
      19 |     description: undefined

      at new ButtonHold (src/components/button-hold.js:16:423)
      at ReactShallowRenderer.render (node_modules/react-test-renderer/cjs/react-test-renderer-shallow.development.js:398:26)
      at node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:480:35
      at withSetStateAllowed (node_modules/enzyme-adapter-utils/build/Utils.js:137:16)
      at Object.render (node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:479:68)
      at new ShallowWrapper (node_modules/enzyme/build/ShallowWrapper.js:204:22)
      at shallow (node_modules/enzyme/build/shallow.js:21:10)
      at Object.<anonymous> (src/components/button-hold.test.js:11:41)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        2.242s

This only happens when the state is defined as class property, when I define it inside the constructor, the test runs normally

Locked

Most helpful comment

Hey @sasurau4 , it worked!
Thanks for the help!

Just for someone that may endup here in this issue, the workaround consists in:

  • Going to the preprocessor.js file inside react-native jest folder, normally located in this path <rootDir>/node_modules/react-native/jest/preprocessor.js and
  • Change the inlineRequires: true to inlineRequires: false

All 9 comments

Could you post a minimal reproduction? 1 component, 1 test, keep adding your specific details until it's reproducible. It is hard to help you without more context.

Sure, here it goes

The test:

import React from 'react'
import { shallow } from 'enzyme'
import ComponentTest from './component'

describe('<Component>', () => {
  const sampleText = 'test'
  const Component = <ComponentTest title={sampleText} />

  describe('Structure', () => {
    it('renders correctly', () => {
      const wrapper = shallow(Component)
      expect(wrapper).toMatchSnapshot()
    })
  })
})

the component with state defined inside constructor, working :

import React, { PureComponent } from 'react'
import { View, Text } from 'react-native'

type Props = {
  title: string
}
export default class TestComponent extends PureComponent<Props> {
  constructor() {
    super()

    this.state = {
      show: true
    }
  }

  render() {
    return (
      <View>
        {this.state.show && <Text>{this.props.title}</Text>}
      </View>
    )
  }
}

The test result for the above example:

$ node node_modules/jest/bin/jest.js
 PASS  src/components/component.test.js
 › 1 snapshot written.

The component with state class property :

import React, { PureComponent } from 'react'
import { View, Text } from 'react-native'

type Props = {
  title: string
}
export default class TestComponent extends PureComponent<Props> {
  state = {
    show: true
  }

  render() {
    return (
      <View>
        {this.state.show && <Text>{this.props.title}</Text>}
      </View>
    )
  }
}

The test result for the above example:

 FAIL  src/components/component.test.js
  ● <Component> › Structure › renders correctly

    TypeError: Cannot read property 'default' of undefined

       5 |   title: string
       6 | }
    >  7 | export default class TestComponent extends PureComponent<Props> {
         |                                                                                                                                                           ^
       8 |   state = {
       9 |     show: true
      10 |   }

      at new TestComponent (src/components/component.js:7:419)
      at ReactShallowRenderer.render (node_modules/react-test-renderer/cjs/react-test-renderer-shallow.development.js:398:26)
      at node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:480:35
      at withSetStateAllowed (node_modules/enzyme-adapter-utils/build/Utils.js:137:16)
      at Object.render (node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js:479:68)
      at new ShallowWrapper (node_modules/enzyme/build/ShallowWrapper.js:204:22)
      at shallow (node_modules/enzyme/build/shallow.js:21:10)
      at Object.<anonymous> (src/components/component.test.js:11:41)

Thanks for the help

@KalebPortillo
Additionally it's better to post jest config from reproduce exmple's package.json or jest.config.js.

If you use jest/preprocessor.js as transformer, #22175 would be helpful.

Jest config file:

import { configure } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'

configure({ adapter: new Adapter() })

packege.json

{
  "name": "mora",
  "version": "0.0.1",
  "private": true,
  "scripts": {
   ....
  },
  "dependencies": {
    "apisauce": "^1.0.0",
    "react": "^16.6.3",
    "react-native": "^0.57.5",
    "react-native-config": "^0.11.5",
    "react-native-fast-image": "^5.1.1",
    "react-native-firebase": "^5.0.0",
    "react-native-google-signin": "^1.0.1",
    "react-native-splash-screen": "^3.0.6",
    "react-navigation": "^2.18.1",
    "react-navigation-redux-helpers": "^2.0.8",
    "react-redux": "^5.0.7",
    "redux": "^3.7.2",
    "redux-logger": "^3.0.6",
    "redux-persist": "^5.9.1",
    "redux-thunk": "^2.2.0",
    "reselect": "^3.0.1"
  },
  "devDependencies": {
    "@babel/cli": "^7.1.5",
    "@babel/core": "^7.1.6",
    "@babel/plugin-proposal-class-properties": "^7.1.0",
    "@babel/preset-env": "^7.1.6",
    "@babel/runtime": "^7.1.5",
    "babel-eslint": "^10.0.1",
    "babel-jest": "^23.6.0",
    "babel-plugin-jest-hoist": "^23.2.0",
    "enzyme": "^3.7.0",
    "enzyme-adapter-react-16": "^1.7.0",
    "enzyme-to-json": "^3.3.4",
    "eslint": "^5.9.0",
    "eslint-config-airbnb": "^17.1.0",
    "eslint-config-prettier": "^3.3.0",
    "eslint-plugin-flowtype": "^3.2.0",
    "eslint-plugin-import": "^2.14.0",
    "eslint-plugin-jsx-a11y": "^6.1.2",
    "eslint-plugin-react": "^7.11.1",
    "fs-extra": "^6.0.1",
    "husky": "^1.2.0",
    "jest": "23.6.0",
    "lint-staged": "^8.1.0",
    "metro-react-native-babel-preset": "^0.49.2",
    "prettier": "^1.15.2",
    "react-dom": "^16.6.3",
    "react-test-renderer": "^16.6.1",
    "replace-in-file": "^3.4.0",
    "schedule": "0.4.0"
  },
  "jest": {
    "preset": "react-native",
    "transform": {
      "^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
    },
    "testMatch": [
      "**/?(*.)test.js?(x)"
    ],
    "snapshotSerializers": [
      "enzyme-to-json/serializer"
    ],
    "setupFiles": [
      "<rootDir>/jest/setup.js"
    ]
  },
  "lint-staged": {
    "*.js": [
      "yarn pretty",
      "git add"
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged && yarn test",
      "pre-push": "lint-staged && yarn test"
    }
  },
  "rnpm": {
    "assets": [
      "./src/assets/fonts/"
    ]
  },
  "babel": {
    "presets": [
      "module:metro-react-native-babel-preset"
    ],
    "plugins": [
      "@babel/plugin-proposal-class-properties",
      "jest-hoist"
    ]
  }
}

Same issue. If the component has a class property, e.g.:

export class MyComponent extends Component {
  state = {
    showButtons: true
  }
 ...

getting the error TypeError: Cannot read property 'default' of undefined when running a jest test.

I've got the same problem on React Native 0.57.7.

It's strange because the Jest preprocessor <rootDir>/node_modules/react-native/jest/preprocessor.js explicitly uses the @babel/plugin-proposal-class-properties plugin if you inspect its code:

plugins: [
        [require('@babel/plugin-transform-block-scoping')],
        // the flow strip types plugin must go BEFORE class properties!
        // there'll be a test case that fails if you don't.
        [require('@babel/plugin-transform-flow-strip-types')],
        [
          require('@babel/plugin-proposal-class-properties'),
          // use `this.foo = bar` instead of `this.defineProperty('foo', ...)`
          {loose: true},
        ],
      ...

Any workarounds?

@KalebPortillo
Thanks for your post.
I reproduced https://github.com/facebook/react-native/issues/22437#issuecomment-442480938.

The workaround is https://github.com/facebook/react-native/issues/22175#issuecomment-439988478.
I confirmed the reproduced test passed fine when using this workaround.

Hey @sasurau4 , it worked!
Thanks for the help!

Just for someone that may endup here in this issue, the workaround consists in:

  • Going to the preprocessor.js file inside react-native jest folder, normally located in this path <rootDir>/node_modules/react-native/jest/preprocessor.js and
  • Change the inlineRequires: true to inlineRequires: false

This should obviously be fixed but closing this issue as a duplicate of #22175 to concentrate info there

@kelset close

Was this page helpful?
0 / 5 - 0 ratings

Related issues

josev55 picture josev55  ·  3Comments

jlongster picture jlongster  ·  3Comments

phongyewtong picture phongyewtong  ·  3Comments

despairblue picture despairblue  ·  3Comments

WG-Com picture WG-Com  ·  3Comments