Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Having trouble with enzyme mount of tree that depends on theme #106

Closed
duro opened this issue Dec 5, 2017 · 20 comments
Closed

Having trouble with enzyme mount of tree that depends on theme #106

duro opened this issue Dec 5, 2017 · 20 comments

Comments

@duro
Copy link

duro commented Dec 5, 2017

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?

@MicheleBertoli
Copy link
Member

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!

@alexpate
Copy link

alexpate commented Feb 9, 2018

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!

@MicheleBertoli
Copy link
Member

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!

@froyog
Copy link

froyog commented Feb 21, 2018

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

@MicheleBertoli
Copy link
Member

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

@froyog
Copy link

froyog commented Feb 25, 2018

@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

@alexpate
Copy link

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.

@michalpleszczynski
Copy link

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 ?

@poffdeluxe
Copy link

Has anyone found any good solutions to this issue? I'm also currently running into this problem

@alexpate
Copy link

alexpate commented Mar 14, 2018

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

@poffdeluxe
Copy link

poffdeluxe commented Mar 14, 2018

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 enzymejs/enzyme#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

@MicheleBertoli
Copy link
Member

This is awesome, thank you very much @poffdeluxe.
Do you mind submitting a PR which adds this information to the README?

@jakelazaroff
Copy link
Contributor

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.

@MicheleBertoli
Copy link
Member

That's a very good point, @jakelazaroff.
Do you mind submitting a PR here as well, @poffdeluxe?
Thank you very much!

@poffdeluxe
Copy link

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

@MicheleBertoli
Copy link
Member

Thank you very much, @poffdeluxe.

@DavidHenri008
Copy link

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)

@DavidHenri008
Copy link

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.

@fabb
Copy link

fabb commented Mar 13, 2019

getChildContext is part of the old and deprecated React Context api. We should find a different solution. Any ideas?

@eric-burel
Copy link

eric-burel commented Jun 28, 2021

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: #217

Limitation so far: it wraps your component with another component so it may break test that use .state() for instance, I am investigating a better version. Edited the code with a cleaner version, using wrappingComponent. All my tests are passing again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants