enzyme: shallow doesn't work correctly with useState + React.memo

Current behavior

Hi all! I try to test my functional component, wrapped by memo.

TestButton.tsx


function TestButton () {
  const [open, setOpen] = useState(false)
  const toggle = () => setOpen(!open)
  return (
    <button
      className={open && 'Active'}
      onClick={toggle}>
      test
    </button>
  )
}


export default memo(TestButton)

TestButton.test.tsx

import React from 'react'
import { shallow } from 'enzyme';
import TestButton from './TestButton';

describe('Test', () => {
  it('after click, button should has Active className ', () => {
    let component = shallow(<TestButton />)
    component.find("button").prop('onClick')()
    expect(component.find("button").hasClass('Active')).toBeTruthy()
  })
})

I expect, that test will pass, but it fails and i can not understand why. If I will remove memo wrapper it passed. Or if I wrap testing component with mount and after click make component.update() it will be passed too

Expected behavior

Test should be passed

Your environment

API

  • shallow
  • mount
  • render

Version

library version
enzyme 3.1.0
react 16.8.0
react-dom 16.8.0
react-test-renderer
adapter (below)

Adapter

  • enzyme-adapter-react-16
  • enzyme-adapter-react-16.3
  • enzyme-adapter-react-16.2
  • enzyme-adapter-react-16.1
  • enzyme-adapter-react-15
  • enzyme-adapter-react-15.4
  • enzyme-adapter-react-14
  • enzyme-adapter-react-13
  • enzyme-adapter-react-helper
  • others ( )

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 10
  • Comments: 18 (5 by maintainers)

Most upvoted comments

Any solution for this test case?

Maybe just split exports then you can avoid “React.memo()” versions in your tests

// Component.js
export const Component = (props) => { ... }

export default React.memo(Component)

// Component.test.js
import { Component } from './Component.js' 

// Container.js
import Component from './Component.js'

@frayeralex code should not depend on writing tests

I had a similar issue you’re facing. I’m shallow rendering a component with memoized components on the inside, and was not seeing the updates in the wrapper.debug() output. The problem seems pretty obvious once I figured it out.

React.memo is checking your props for equality before re-rendering (that’s great, that’s why we’re using memo!). If the props don’t change, the component won’t update.

Example Component

const MyComponent = React.memo((props: { testProp: boolean }) => {
  const [myState, setMyState] = useState('')
  return <input value={myState} onChange={e => setMyState(e.target.value} />
})

Example Test

it('updates', () => {
  const wrapper = enzyme.shallow(<MyComponent testProp={true} />
  wrapper.simulate('change', { target: { value: 'abc' } })
  expect(wrapper.prop('value')).toEqual('abc') // FAIL
})

To recap: our props didn’t change, so our component should not re-render.

How do we solve this? Change the props!

Updated Test

it('updates', () => {
  const wrapper = enzyme.shallow(<MyComponent testProp={true} />
  wrapper.simulate('change', { target: { value: 'abc' } })
  wrapper.setProps({ testProp: false })
  expect(wrapper.prop('value')).toEqual('abc') // FAIL
})

This technique may not work for everyone; for example, if you’re relying on your props to be a certain primitive value, you might not be able to change it. In my case, I set the same prop values, but re-created the prop objects so that a strict equality check on prop objects would equate to false.

const props = () => ({ testPropObject: { ...testFixture } })
props() === { ...props() } // false

I’ll keep this open, to track it.

Not sure if related but memo doesn’t seem to work with mount.

const MyComponent = memo(({ children, condition }) => condition ? children : null );

When I use enzyme to test it:

const wrap1 = mount(<MyComponent condition={true}>{children}</MyComponent>; // test passes when asserting `children` is `!null`

const wrap2 = mount(<MyComponent condition={false}>{children}</MyComponent>; //test fails when asserting `children === null`



If I use it without memo than it’s all good

React: 16.9.0 React-DOM: 16.9.0 Enzyme: 3.10.0 enzyme-adapter-react-16: 1.14.0

"react": "^16.8.6",
"react-dom": "^16.8.6",

@ljharb Unfortunately, upgrate to latest version didn’t help. Same behavior

yes, of course