I know this is a topic that has been marked as "tricky" but I could really use some help to understand how to best go about testing a tree that has components that depend on theme using enzyme's mount rendering.
I have tried the following solutions to no avail:
import React from 'react'
import { mount, shallow } from 'enzyme'
import { ThemeProvider } from 'styled-components'
import createBroadcast from 'styled-components/lib/utils/create-broadcast'
import theme from 'theme'
const CHANNEL = '__styled-components__'
const broadcast = createBroadcast(theme)
const nodeWithThemeProp = node => {
return React.cloneElement(node, { [CHANNEL]: broadcast.subscribe })
}
export const mountWithTheme = (node, { context, childContextTypes } = {}) => {
return mount(nodeWithThemeProp(node), {
context: Object.assign({}, context, { [CHANNEL]: broadcast.subscribe }),
childContextTypes: Object.assign(
{},
{ [CHANNEL]: createBroadcast(theme).publish },
childContextTypes
)
})
}
AND:
export const mountWithTheme = tree => {
const context = shallow(<ThemeProvider theme={theme} />)
.instance()
.getChildContext()
return mount(tree, { context })
}
Any direction you could provide?
Hello @duro, thanks for opening this issue.
Do you mind elaborating more on the problem you are trying to solve?
The known "issue" is related to Theme and shallow rendering, mount should work as expected.
Thank you very much!
I'm having an issue with this as well.
shallow works okay, but trying to mount a component that has a nested styled-component in it, doesn't work.
This is what my test looks like:
const context = shallow(<ThemeProvider theme={theme} />).instance().getChildContext();
const element = mount(<MyComponent />, {context});
MyComponent is a form component that contains, among other things, several input components that are styled-components.
The error I get back is Cannot read property 'fontWeightMedium' of undefined.
A console.log inside the nested styled-component shows that the theme prop isn't defined.
Any help would be greatly appreciated!
Hello @alexpate, I'll be more than happy to help.
However, I'm not able to repro the issue.
test('theming', () => {
const Button = styled.button`
color: ${props => props.theme.main};
`
const theme = {
main: 'mediumseagreen',
}
const context = shallow(<ThemeProvider theme={theme} />)
.instance()
.getChildContext()
const element = mount(<Button />, { context })
expect(element).toMatchSnapshot()
})
exports[`theming 1`] = `
.c0 {
color: mediumseagreen;
}
<styled.button>
<button
className="c0"
/>
</styled.button>
`;
Would you mind providing a non-working example?
Thank you very much!
@MicheleBertoli
for example
<MyComponent>
<AnotherComponent>something important</AnotherComponent>
</MyComponent>
import MyComponent from './MyComponent';
test('themeing', () => {
const theme = {
main: 'mediumseagreen',
}
const context = shallow(<ThemeProvider theme={theme} />)
.instance()
.getChildContext()
const wrapper = mount(<MyComponent />, { context })
// expect ...
})
In the snippet above MyComponent will get the context but the nested AnotherComponent is not because you didn't pass the context directly to it. I think the key is to "mount with theme context" recursively. I have no idea how to solve this problem, hope to get some help.
Oh, I see - it seems Enzyme injects the context only in the root component.
Any reason why you are manually setting the context instead of rendering the theme provider (example)?
@MicheleBertoli I'd like to use setState() or setProps() whick can only be called on the root element.
Rendering a <ThemeProvider theme={{}}><MyComponent /></ThemeProvider> makes ThemeProvider the root element instead of MyComponent
Apologies for the slow reply, have been away for the past week or so.
We are running in to the same problem that @froyog has outlined. We can use the ThemeProvider in the tests, however lose out on a lot of the nice Jest stuff.
We're having the same issue as well. Reading through other similar issue in this repo and styled-components repo it seems that so far the solutions only apply to using shallow. Are there any more suggestions for: mount + styled-component + being able to use setProps()/setState()/etc ?
Has anyone found any good solutions to this issue? I'm also currently running into this problem
@poffdeluxe At the moment, there doesn't seem to be a full solution.
You can access some of the root methods, by calling instance() first, and then calling the method:
To access state for example:
const wrapper = mount(
<ThemeProvider theme={theme}>
<Button />
</ThemeProvider>
);
wrapper.find('Button').instance().state;
http://airbnb.io/enzyme/docs/api/ReactWrapper/instance.html
But methods such as setState and setProps won't work at the moment.
Gotcha.
I think I might have figured out the workaround:
const mountWithTheme = (tree, theme) => {
const context = shallow(<ThemeProvider theme={theme} />)
.instance()
.getChildContext();
return mount(tree, {
context,
childContextTypes: ThemeProvider.childContextTypes
});
};
From what I understand following the discussion on https://github.com/airbnb/enzyme/issues/144 it looks like the childContextTypes need to be passed to the mount along with the context else the children won't receive them
My child components are now receiving theme data on full mounts
This is awesome, thank you very much @poffdeluxe.
Do you mind submitting a PR which adds this information to the README?
Any chance you could add this to the test utilities documentation on the official website as well? This entirely solved my problem, but I didn't think to check the README in addition to the docs on the website until I found this issue.
That's a very good point, @jakelazaroff.
Do you mind submitting a PR here as well, @poffdeluxe?
Thank you very much!
Sorry for the delay, I've been super busy last few weeks. I'll put up a PR for the styled-components website when I can
Thank you very much, @poffdeluxe.
This solution is not working for me because of the getChildContext() function call.
const mountWithTheme = (children) => {
const context = shallow(<ThemeProvider theme={theme} />)
.instance()
.getChildContext();
return mount(children, {
context,
childContextTypes: ThemeProvider.childContextTypes,
});
};
Produces the following error:
TypeError: (0 , _enzyme.shallow)(...).instance(...).getChildContext is not a function
13 | const context = shallow(<ThemeProvider theme={theme} />)
14 | .instance()
> 15 | .getChildContext();
| ^
16 |
17 | return mount(children, {
18 | context,
at getChildContext (src/components/Title/__tests__/Title.test.js:15:6)
at Object.mwt (src/components/Title/__tests__/Title.test.js:27:21)
The getChildContext() issue seems related to the new styled-components v4. However I found this hacky solution that works for me.
A solid solution and explanation may be welcome.
getChildContext is part of the old and deprecated React Context api. We should find a different solution. Any ideas?
For lost googlers, if you have a null instance from getInstance, that means you upgraded from React 15 to React 16. Stateless components have no instance anymore ([related Enzyme doc here])(https://enzymejs.github.io/enzyme/docs/api/ShallowWrapper/instance.html).
Instead use simpler version:
export const mountWithTheme = (tree, theme = defaultTheme) => {
return mount(tree, {
wrappingComponent: ({ children }) => (
<ThemeProvider theme={theme}>{children}</ThemeProvider>
)
});
};
See this issue as well: https://github.com/styled-components/jest-styled-components/issues/217
Limitation so far: it wraps your component with another component so it may break test that use Edited the code with a cleaner version, using .state() for instance, I am investigating a better version.wrappingComponent. All my tests are passing again.
Most helpful comment
Gotcha.
I think I might have figured out the workaround:
From what I understand following the discussion on https://github.com/airbnb/enzyme/issues/144 it looks like the
childContextTypesneed to be passed to the mount along with the context else the children won't receive themMy child components are now receiving theme data on full mounts