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

Testing styled component with enzyme #624

Closed
chuanxie opened this issue Mar 28, 2017 · 33 comments
Closed

Testing styled component with enzyme #624

chuanxie opened this issue Mar 28, 2017 · 33 comments

Comments

@chuanxie
Copy link

chuanxie commented Mar 28, 2017

I got theme defined in the app and the child component contains style like
color: ${props => props.theme.options.color};

if i use Enzyme to mount the parent component, it cause TypeError: Cannot read property of undefined

Is there any solution for this issue, i try to setContext(__styled-component__) in the test but it doesn't work.

@k15a k15a added the question label Mar 28, 2017
@chuanxie
Copy link
Author

chuanxie commented Mar 29, 2017

created a workaround but it might not be the best solution.

const CHANNEL = '__styled-components__';
const broadcast = createBroadcast(themes.dark);

const nodeWithThemeProp = node => {
  return React.cloneElement(node, { [CHANNEL]: broadcast.subscribe });
};

export function mountWithTheme(node, { context, childContextTypes } = {}) {
  return mount(
    nodeWithThemeProp(node),
    {
      context: Object.assign({}, context, {[CHANNEL]: broadcast.subscribe}),
      childContextTypes: Object.assign({}, {[CHANNEL]: createBroadcast(themes.dark).publish}, childContextTypes)
    }
  );
}

now i can get the wrapper component's state() by using:
const wrapper = mountWithTheme(<Component {...props} />);

@kitten
Copy link
Member

kitten commented Mar 30, 2017

Why don't you just use an actual Theme provider? Surely the code will end up looking less hacky.

@kitos
Copy link

kitos commented Apr 3, 2017

@philpl in this case it would be imposible to use shallow render of enzyme, cause the only rendered element will be ThemeProvider.
So I'm still interested in less hacky way to test styled-components.

@insidewhy
Copy link

Can we reopen this, none of the current ways of testing react together with styled components are adequate, @chuanxie's hack works but it's still a hack.

@kitten
Copy link
Member

kitten commented Apr 28, 2017

@kitos So what's the problem with using a full mount? :/

cc @nfcampos

@kitten kitten reopened this Apr 28, 2017
@kitos
Copy link

kitos commented Apr 28, 2017

@philpl first of all I want to test my components in isolation, so shallow rendering is what I need.
What about full mount, I don't know how to select nodes I need to assert, cause classnames are just hash, and I think using nested selector (like ul li a) is not very nice too.

@nfcampos
Copy link

would this work?

shallow(<Component />).dive()

documentation here https://github.com/airbnb/enzyme/blob/master/docs/api/ShallowWrapper/dive.md

@insidewhy
Copy link

insidewhy commented Apr 28, 2017

The same problem described in the topic exists regardless of whether you shallow mount or full mount... if you add the ThemeProvider as the "root component" you lose access to the root node specific functions provided by testing helper libraries like enzyme, regardless of whether you shallow or deep render.

@matthewvincent
Copy link

I'm looking for a way to shallow render test my styled components in Enzyme as well and struggling. My previous tests would follow a simple wrapper.render.find('.selector') or expect(wrapper.html()).toEqual('...')) but I now have no way of accurately referencing the hashes. I've used css modules in these tests in the past and found it easy to reference the json map provided for the generated hashes but as far as I know there is no equivalent here. Any suggestions for a workaround would be greatly appreciated!

@insidewhy
Copy link

@matthewvincent the solution is further up the thread, see my previous comment.

@gregberge
Copy link
Contributor

My workaround for glamorous, should work with styled-components too:

const shallowWithTheme = (children, options) => {
  const wrapper = shallow(<ThemeProvider theme={theme}>{children}</ThemeProvider>, options)
  const instance = wrapper.root.instance()
  return wrapper.shallow({ context: instance.getChildContext() })
}

shallowWithTheme(<components.Button>Hello</components.Button>)

@connorsmallman
Copy link

connorsmallman commented Jun 2, 2017

Anyone know how to solve this when using react-test-renderer?

@ronnyhaase
Copy link

I agree that it's hard to test using themes powered by ThemeProvider. I think @neoziro 's solution is very smart, and maybe a note regarding this in the docs would be nice.

Allow me to side-note that currently my thought is not using a provider and instead have the theme in a raw JS file and importing it directly is smarter. It avoids exactly this testing issue that I've seen many people run into multiple times.
Also I can simply interpolate directly instead of this anonymous functions taking theme from props what probably is also more performant. color: ${importedRawTheme.textColor} instead of color: ${({theme}) => theme.textColor}
Correcting me that I'm wrong would be appreciated. 😉

@three60five
Copy link

When I use styled-components to make "building block" components defined in the same file as the "assembled" component, I am having a really difficult time targeting them in my tests using enzyme's .find() method.

For example, with this styled-component:

import ImportedComponent from './ImportedComponent';
const Demo = styled.div`
`;
const Label = styled.span`
`;
const Thing = () =>
  <Demo>
    <ImportedComponent />
    <Label />
    <Label /> 
    <Label />
  </Demo>;

If I use an imported styled-component like <ImportedComponent>, I can easily find it like with enzyme like:

describe('<Thing />', () => {
  it('should contain an ImportedComponent', () => {
    const wrapper = shallow(<Thing />);
    const importedComponent = wrapper.find('ImportedComponent');
    expect(importedComponent).to.have.length(1);
  });
});

But I haven't found an elegant way to find the "building block" components like <Label /> because they can't be found by the component name as they appear more generically like "<styled.span>". Is there a way for these "building block" components to appear with their component name like they are when imported? I don't want to resort to targeting them by html or css as that is more brittle.

@dagda1
Copy link

dagda1 commented Jun 10, 2017

as @nfcampos stated, the only way I have found is to use dive:

describe('Label', () => {
  it('should render label and text', () => {
    const wrapper = shallow(<Label required>Some Text</Label>).dive();

    expect(wrapper.text()).toBe('Some Text');
  });
});

@insidewhy
Copy link

chuanxie has the best solution further up this thread, it covers shallow rendering and access to root-specific methods.

@butlersrepos
Copy link

@chuanxie Is this for testing styled-components outside of this repo? I don't follow how you're referencing the createBroadcast function.

@insidewhy
Copy link

Ah he left that out:

import createBroadcast from 'styled-components/lib/utils/create-broadcast';

@insidewhy
Copy link

His code can be simplified a little too, did it for a client I work for, unfortunately can't post it right now.

@butlersrepos
Copy link

butlersrepos commented Jun 10, 2017

I tried the direct import like that and ran into the "fun" problem of Jest transforming the import. Isn't jest supposed to ignore node_modules for transforms by default? :| I've run into this several times, perhaps I should bring it up on Jests github, unless I'm misunderstanding what this issue is?

image

@insidewhy
Copy link

You talking about auto-mocking?

@butlersrepos
Copy link

I thought that the error I posted a snapshot of was Jest trying to trasnform an import improperly? automocking is defaulted to off.

@insidewhy
Copy link

No idea, I'm also using it with jest at BulbEnergy and didn't have that problem.

@insidewhy
Copy link

It looks like you are trying to import ES6 module syntax, which isn't supported by any existing version of node. Are you sure you used the right path for your broadcast import?

@three60five
Copy link

Is there anything like jest-styled-components for enzyme? I want the toHaveStyleRule API specifically.

@luftywiranda13
Copy link
Contributor

@three60five you can do it now with jest-styled-components.
try to implement #624 (comment)

@insidewhy
Copy link

Again you keep linking to that comment when the better solution is up the page.

@dagda1
Copy link

dagda1 commented Jun 20, 2017

@ohjames why is this a better solution?

@insidewhy
Copy link

It supports access to root node specific methods on non-shallow renders in addition to the shallow rendering case. Also faster.

@JimmyLv
Copy link

JimmyLv commented Aug 7, 2017

Wrapping with ThemeProvider in test will also meet some issue with enzyme:

export const mountWithTheme = (children, options) => {
  const wrapper = mount(<ThemeProvider theme={injectTheme}>{children}</ThemeProvider>, options);
  const instance = wrapper.root.instance();
  return wrapper.mount({ context: instance.getChildContext() });
};
const wrapper = mountWithTheme(<DataContainer
        configuration={testConfig}
        authClient={authClient}
      />);

expect(wrapper.state().dataSummaries).to.deep.equal([]);

as you can have a try, the state() method from enzyme is only work for root wrapper, so that we can't get the state of the component who is wrapped by ThemeProvider.

Any idea or walkaround? 😢

@insidewhy
Copy link

The workaround has already been pasted in this very thread.

@kitten
Copy link
Member

kitten commented Aug 7, 2017

@ohjames @JimmyLv I've been following this for a while w/o actually pointing out the obvious solution. Sorry for that!

https://github.com/styled-components/styled-components/blob/master/src/models/StyledComponent.js#L100

When there's no ThemeProvider context present, the StyledComponent defaults to the actual theme prop. This means that you can just pass theme as a prop manually or set it on defaultProps inside your tests.

@chuanxie's workaround works perfectly as well, but will probably need some modifications after #1048 is merged.

Btw, it's undesirable to test the entire ThemeProvider flow instead of just passing the prop or doing other, similar things anyway, since it tests our code. You don't need to test for styled-components to work correctly, since that's our responsibility 😉

cc @MicheleBertoli Can we add a utility to test this to jest-styled-components, even if it ends up being dead simple? This way we can be sure that no one asks themselves this question anymore 😄

If we can get a nice guide for our docs, or—what I'd prefer—a small utility on jest-styled-components, then I'm going to go ahead and lock this issue, as it has generated a lot of noise.

Edit: I've opened an issue on jest-styled-components to write a helper or some docs to solve this problem in a central place. styled-components/jest-styled-components#61

This issue has been really noisy and not helpful for people to spot a workaround. If you're landing here and are searching for a solution, either use the theme prop (as pointed out in this comment), or @chuanxie's workaround (2nd comment).

If you think this issue should continue, don't open a new one, but msg me on Twitter or Gitter, or post at the new jest-styled-components issue to work on a solution please

@kitten kitten closed this as completed Aug 7, 2017
@insidewhy
Copy link

The same hacks are needed for ApolloProvider and many others so it'd be good to provide a solution for it in enzyme I think. They should allow dive() even in non-shallow components.

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

No branches or pull requests