Hi all! I try to test my functional component, wrapped by memo.
```import React, { memo, useState } from 'react'
function TestButton () {
const [open, setOpen] = useState(false)
const toggle = () => setOpen(!open)
return (
className={open && 'Active'}
onClick={toggle}>
test
)
}
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(
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 withmountand after click makecomponent.update()` it will be passed too
Test should be passed
| library | version
| ------------------- | -------
| enzyme | 3.1.0
| react | 16.8.0
| react-dom | 16.8.0
| react-test-renderer |
| adapter (below) |
Try upgrading react and react-dom to latest?
@ljharb yes
@MellowoO to clarify - you specified you're on 16.8.0, but there's a number of bugs in the early 16.8 releases. Can you upgrade to the latest react and react-dom, confirm exactly which versions you're on, and what behavior you're seeing?
yes, of course
"react": "^16.8.6",
"react-dom": "^16.8.6",
@ljharb
Unfortunately, upgrate to latest version didn't help. Same behavior
So, in this case, it seems the use of the useState hook combined with memo may be the issue. If you use a class component and setState, it will work.
This is likely a limitation in react鈥檚 shallow renderer, which enzyme uses. Since hooks give us no way to hook into them, there鈥檚 not really a way for enzyme to react to hook changes.
@ljharb
Oh, this is badly
Anyway, thank you for help, I'll wait for updates)
I鈥檒l 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
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.
const MyComponent = React.memo((props: { testProp: boolean }) => {
const [myState, setMyState] = useState('')
return <input value={myState} onChange={e => setMyState(e.target.value} />
})
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!
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
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
any updates on this issue ??
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.3",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-test-renderer": "^16.13.1"
// Header.js
import React, { memo, useState } from "react";
const Header = () => {
const [value, setValue] = useState("");
const handleInputChange = (e) => {
setValue(e.target.value.trim());
};
return (
<div>
<input
type="text"
data-test="input"
value={value}
onChange={handleInputChange}
name="todo-input"
/>
</div>
);
};
export default memo(Header);
// Header.test.js
it("Header 缁勪欢 input 妗嗗唴瀹癸紝褰撶敤鎴疯緭鍏ユ椂锛屼細璺熼殢鍙樺寲", () => {
const wrapper = shallow(<Header />);
wrapper.find("input[data-test='input']").simulate("change", {
target: {
value: "learn jest",
},
});
const userInput = "learn jest";
expect(wrapper.find("input[data-test='input']").prop("value")).toEqual(
userInput
);
});
expect(received).toEqual(expected) // deep equality
Expected: "learn jest"
Received: ""
22 | });
23 | const userInput = "learn jest";
> 24 | expect(wrapper.find("input[data-test='input']").prop("value")).toEqual(
| ^
25 | userInput
26 | );
27 | });
at Object.<anonymous> (src/containers/todo-list/__tests__/unit/Header.test.js:24:66)
any updates on this issue ??
"jest": "^25.1.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"react": "16.12.0",
"react-dom": "16.12.0"
describe('test', () => {
it('success suite', () => {
const Component: React.FC = () => null;
const $el = mount(
<Component>
<div id={'findMe'} />
</Component>
);
expect($el.exists('#findMe')).toBeFalsy(); // passed
});
it('failure suite', () => {
const Component: React.FC = React.memo(() => null);
const $el = mount(
<Component>
<div id={'findMe'} />
</Component>
);
expect($el.exists('#findMe')).toBeFalsy(); // fail
});
});
debug output on failure suite:
<Memo()>
<div id="findMe" />
</Memo()>
Most helpful comment
Any solution for this test case?