Enzyme: Testing HOC returns null in wrapper.instance()

Created on 30 Nov 2016  路  12Comments  路  Source: enzymejs/enzyme

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.

invalid question

Most helpful comment

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 :)

All 12 comments

<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.

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

})

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

@ljharb Thanks. Didn't know about dive.

@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.

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

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 }

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.

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.

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

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

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 :)

Was this page helpful?
0 / 5 - 0 ratings