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

Testing HOC returns null in wrapper.instance() #711

Closed
Mottoweb opened this issue Nov 30, 2016 · 12 comments
Closed

Testing HOC returns null in wrapper.instance() #711

Mottoweb opened this issue Nov 30, 2016 · 12 comments

Comments

@Mottoweb
Copy link

Hi. I need some help here as i am quite stuck here.

I have a HOC

import React, { Component } from 'react'

const isSmallScreenRender = (WrappedComponent) =>
  class extends Component {
    constructor(props) {
      super(props)
      this.handleResize = ::this.handleResize
    }
    componentWillMount() {
      this.handleResize()
    }

    componentDidMount() {
      window.addEventListener('resize', this.handleResize)
    }

    componentWillUnmount() {
      window.removeEventListener('resize', this.handleResize)
    }

    handleResize() {
      const isSmallScreen = window.innerWidth <= 751
      this.setState({
        isSmallScreen,
      })
    }

    render() {
      const newProps = {
        isSmallScreen: this.state.isSmallScreen,
      }
      return <WrappedComponent {...this.props} {...newProps} />
    }
  }

export default isSmallScreenRender

And i want to test its internal methods with instance() of shallow.
Here is my test.

import React              from 'react'
import { mount, shallow }          from 'enzyme'
import isSmallScreen
from '../../../../app/scripts/kyc/signup/components/screen-based-render-hoc'

jest.unmock('../../../../app/scripts/kyc/signup/components/screen-based-render-hoc')

describe('isSmallScreenRender() no wrapping', () => {

  let wrapper
  let instance

  beforeEach(() => {
    wrapper = shallow(<isSmallScreen />)
    instance = wrapper.instance()
  })

  it('Sets isSmallScreen to false if window.innerWidth is more than 751', () => {
    window.innerWidth = 752
    console.log(wrapper.instance())
    expect(instance.state('isSmallScreen')).toBe(false)
  })

  it('Sets isSmallScreen to true if window.innerWidth is less than 751', () => {
    window.innerWidth = 600
    expect(instance.state('isSmallScreen')).toBe(true)
  })

})

describe('isSmallScreenRender()', () => {

  let wrappedComponent
  let wrapper
  let MockComponent

  beforeEach(() => {
    MockComponent = () =>
      <div>Fake comp</div>

    wrappedComponent = isSmallScreen(MockComponent)
    wrapper = mount(<wrappedComponent />)
  })

  it('exports itself', () => {
    expect(isSmallScreen).toBeDefined()
  })

  it('Renders inner component as the root element', () => {
    expect(wrapper.first(MockComponent)).toBeTruthy()
  })

})

When i am loggin wrapper.instance() in the fist suit i get null.
Am i missing something? I have other tests doing same for classes with no problems.

@laumair
Copy link

laumair commented Nov 30, 2016

<isSmallScreen> is not a composite component. It's a function that returns a component that it receives in arguments. So, in order to get the instance for the component you would have to pass the mock component in the function. const component = isSmallScreen(MockComponent) and then you can do const wrapper = shallow<component /> @Mottoweb I haven't tested this myself but I am pretty sure this would work.

@Mottoweb
Copy link
Author

Mottoweb commented Dec 1, 2016

@laumair Thanks for your reply!

This is what i though at first, but the function returns a component that renders another component.
I need to test my HOC as a unit itself. Without returning a component being wrapped to test its methods.

So when doing what you suggested, i still get null on instance().

Doing mount instead of shallow returns this instance

HTMLUnknownElement {
      '__reactInternalInstance$5n4xo7d5hi3l5rt5k7rv0a4i': 
       ReactDOMComponent {
         _currentElement: 
          { '$$typeof': Symbol(react.element),
            type: 'wrappedComponent',
            key: null,
            ref: null,
            props: {},
            _owner: [Object],
            _store: {} },
         _tag: 'wrappedcomponent',
         _namespaceURI: 'http://www.w3.org/1999/xhtml',
         _renderedChildren: null,
         _previousStyle: null,
         _previousStyleCopy: null,
         _hostNode: [Circular],
         _hostParent: null,
         _rootNodeID: 1,
         _domID: 1,
         _hostContainerInfo: 
          { _topLevelWrapper: [Object],
            _idCounter: 2,
            _ownerDocument: [Object],
            _node: HTMLDivElement {},
            _tag: 'div',
            _namespaceURI: 'http://www.w3.org/1999/xhtml',
            _ancestorInfo: [Object] },
         _wrapperState: null,
         _topLevelWrapper: null,
         _flags: 1,
         _ancestorInfo: 
          { current: [Object],
            formTag: null,
            aTagInScope: null,
            buttonTagInScope: null,
            nobrTagInScope: null,
            pTagInButtonScope: null,
            listItemTagAutoclosing: null,
            dlItemTagAutoclosing: null },
         _contentDebugID: null,
         _mountIndex: 0,
         _mountImage: null,
         _debugID: 2 } }

Updated test code

describe('isSmallScreenRender() no wrapping', () => {

  let wrapper
  let instance
  let component
  let MockComponent

  beforeEach(() => {
    MockComponent = () =>
      <div>Fake comp</div>

    component = isSmallScreen(MockComponent)
    wrapper = shallow(<component />)
    instance = wrapper.instance()
  })

  it('Sets isSmallScreen to false if window.innerWidth is more than 751', () => {
    window.innerWidth = 752
    console.log(wrapper.instance())
    expect(instance.state('isSmallScreen')).toBe(false)
  })

  it('Sets isSmallScreen to true if window.innerWidth is less than 751', () => {
    window.innerWidth = 600
    expect(instance.state('isSmallScreen')).toBe(true)
  })

})

@ljharb
Copy link
Member

ljharb commented Dec 1, 2016

If you want to dive through an HOC, try wrapper.dive()

@laumair
Copy link

laumair commented Dec 1, 2016

@ljharb Thanks. Didn't know about dive.

@Mottoweb
Copy link
Author

Mottoweb commented Dec 1, 2016

@ljharb thanks, i will give it a go.

UPD: Went through dive and found out that its there to give you chance to access inner component instance, i.e. not HOC itself. My issue here is that i am not able to access the instance of the HOC to test its methods.

@ljharb
Copy link
Member

ljharb commented Dec 1, 2016

My hunch is that it's related to your jest mocking (i see the unmock call for that component).

@Mottoweb
Copy link
Author

Mottoweb commented Dec 2, 2016

I want to test internal methods of my HOC, i cannot do that if its mocked, so i am unmocking it.
Exporting pure class does not help as well.

ie

import React, { Component } from 'react'

let PureIsSmallScreenRender

const isSmallScreenRender = (WrappedComponent) =>
  PureIsSmallScreenRender = class extends Component {
    constructor(props) {
      super(props)
      this.handleResize = ::this.handleResize
    }
    componentWillMount() {
      this.handleResize()
    }

    componentDidMount() {
      window.addEventListener('resize', this.handleResize)
    }

    componentWillUnmount() {
      window.removeEventListener('resize', this.handleResize)
    }

    handleResize() {
      const isSmallScreen = window.innerWidth <= 751
      this.setState({
        isSmallScreen,
      })
    }

    render() {
      const newProps = {
        isSmallScreen: this.state.isSmallScreen,
      }
      if (!WrappedComponent) return <div />
      return <WrappedComponent {...this.props} {...newProps} />
    }
  }

export default isSmallScreenRender
export { PureIsSmallScreenRender as pureClass }

@ljharb
Copy link
Member

ljharb commented Dec 2, 2016

In that case no, it wouldn't work because the pure component is recreated every time you call isSmallScreenRender.

I do note that you're doing instance.state() - but .state() is a method on wrapper(), not on instance.

@Mottoweb
Copy link
Author

Mottoweb commented Dec 2, 2016

The expect() assertion is not finalized, the main case here is that instance() is null.

I think i should try separate class from function and import it as pure to test, maybe that would help.

@ljharb
Copy link
Member

ljharb commented Dec 2, 2016

Also, in jsx, non-html element component names need to start with a capital letter - try Component instead of component.

@Mottoweb
Copy link
Author

Mottoweb commented Dec 2, 2016

Looks like non-html JSX gotcha got me here, thanks for your help @ljharb @laumair

@dabit1
Copy link

dabit1 commented Feb 1, 2018

I finally found a very good solution to get a wrapper from a decorated component. For shallowed components you can use dive() but there is no similar method for mounted components. This is the solution I did:

const wrapper = mount(shallow(<MyComponent />).get(0))

Works like a charm :)

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

No branches or pull requests

4 participants