When mocking a custom hook, the component is not updated when calling wrapper.update().
Example:
component.jsx
import React from 'react';
import useCustomHook from './custom-hook';
export function MyComponent() {
const hookResult = useCustomHook();
return <span>{hookResult}</span>;
}
component.test.jsx
import { shallow } from 'enzyme';
import React from 'react';
import { MyComponent } from './component';
import useCustomHook from './custom-hook';
jest.mock('./custom-hook', () => jest.fn(() => 'bananas'));
it('should update the component', () => {
expect(wrapper.children().text()).toBe('bananas');
useCustomHook.mockReturnValueOnce('apples');
wrapper.update();
expect(wrapper.children().text()).toBe('apples');
})
The above test passes.
I can get it to work when I replace the wrapper.update() with a wrapper.setProps({}) (which is kinda weird).
So why is the component not re-rendering? According to the docs, wrapper.update() is forcing a re-render. But I guess some optimization kicks in here and prevents the re-render.
| library | version
| ------------------- | -------
| enzyme | 3.10.0
| react | 16.8.6
| react-dom | 16.8.3
| react-test-renderer | 16.8.6
| adapter (below) |
So why is the component not re-rendering? According to the docs, wrapper.update() is forcing a re-render.
The documentation is incorrect I'm afraid. There are two component trees that exist, the actual rendered output from React, and the snapshot taken by Enzyme which is in a standardized format independent of the React version. wrapper.update() updates the snapshot to match the current render output. It doesn't force the underlying component to re-render. See https://github.com/airbnb/enzyme/issues/1405.
I have a similar problem:
useExample.js
const useExample = () => {
return {
getValue: () => true
}
}
export { useExample }
TestComponent.js
import React from "react"
import { useExample } from "./useExample"
const TestComponent = () => {
const example = useExample()
if (example.getValue()) {
return <div>VALUE IS TRUE</div>
}
return <div>VALUE IS FALSE</div>
}
export default TestComponent
TestComponent.test.js
import React from "react"
import Enzyme, { mount, shallow } from "enzyme"
import Adapter from "enzyme-adapter-react-16"
import TestComponent from "./TestComponent"
import { useExample } from "./useExample"
Enzyme.configure({ adapter: new Adapter() })
jest.mock("./useExample", () => {
const useExampleMock = {
getValue: jest.fn(() => true)
}
return {
useExample: () => useExampleMock
}
})
describe("TestComponent", () => {
let wrapper
let getValueSpy
beforeEach(() => {
if (getValueSpy) {
getValueSpy.mockImplementation(() => true)
}
getValueSpy = jest.spyOn(useExample(), "getValue")
wrapper = mount(<TestComponent />)
})
it("cant handle named export without any updates", () => {
expect(wrapper.text()).toEqual("VALUE IS TRUE")
getValueSpy.mockImplementation(() => false)
expect(wrapper.text()).toEqual("VALUE IS FALSE")
})
it("cant handle named export with wrapper update", () => {
expect(wrapper.text()).toEqual("VALUE IS TRUE")
getValueSpy.mockImplementation(() => false)
wrapper.update()
expect(wrapper.text()).toEqual("VALUE IS FALSE")
})
it("can handle named export with wrapper mount", () => {
expect(wrapper.text()).toEqual("VALUE IS TRUE")
getValueSpy.mockImplementation(() => false)
wrapper.mount()
expect(wrapper.text()).toEqual("VALUE IS FALSE")
})
it("can handle named export with new props", () => {
expect(wrapper.text()).toEqual("VALUE IS TRUE")
getValueSpy.mockImplementation(() => false)
wrapper.setProps({})
expect(wrapper.text()).toEqual("VALUE IS FALSE")
})
})
The result is:
✕ cant handle named export without any updates (17ms)
✕ cant handle named export with wrapper update (4ms)
✓ can handle named export with wrapper mount (4ms)
✓ can handle named export with new props (4ms)
@marcus13371337 - This issue is nothing to do with hooks. The documentation for wrapper.update is simply incorrect.
From your test cases:
✕ cant handle named export without any updates (17ms)
This is expected. When you changed the mock return value from your hook, the component wasn't re-rendered and the Enzyme tree was not updated.
✕ cant handle named export with wrapper update (4ms)
This is also expected. wrapper.update() refreshes Enzyme's view of the rendered component tree, but it doesn't re-render the component. For that you need wrapper.setProps({}).
Thinking about this issue at a higher level, although there are good technical reasons for why Enzyme works the way it does (see the Enzyme v2 => v3 migration guide), this is clearly quite confusing for users. This could perhaps be addressed by providing visual documentation to convey the mental model better, but perhaps there are other changes that could be made to avoid the issue?
@robertknight Ok thanks for the clarification, makes sense when you know the difference. It felt hackish to do wrapper.setProps({}). Is that the same "thing" as doing wrapper.mount() or are there any differences between those approaches?
Is that the same "thing" as doing wrapper.mount() or are there any differences between those approaches?
The docs say that wrapper.mount() unmounts and re-mounts the component, whereas setProps({}) re-renders (aka. "updates") the component with new props, which won't trigger lifecycle hooks related to mounting and unmounting.
What you can do as a workaround is mount and then update within the same test block.
import { shallow } from 'enzyme';
import React from 'react';
import { MyComponent } from './component';
import useCustomHook from './custom-hook';
jest.mock('./custom-hook', () => jest.fn(() => 'bananas'));
it('should update the component', () => {
expect(wrapper.children().text()).toBe('bananas');
useCustomHook.mockReturnValueOnce('apples');
+ wrapper.mount()
wrapper.update();
expect(wrapper.children().text()).toBe('apples');
})
Most helpful comment
@marcus13371337 - This issue is nothing to do with hooks. The documentation for
wrapper.updateis simply incorrect.From your test cases:
This is expected. When you changed the mock return value from your hook, the component wasn't re-rendered and the Enzyme tree was not updated.
This is also expected.
wrapper.update()refreshes Enzyme's view of the rendered component tree, but it doesn't re-render the component. For that you needwrapper.setProps({}).Thinking about this issue at a higher level, although there are good technical reasons for why Enzyme works the way it does (see the Enzyme v2 => v3 migration guide), this is clearly quite confusing for users. This could perhaps be addressed by providing visual documentation to convey the mental model better, but perhaps there are other changes that could be made to avoid the issue?