Enzyme: Can't get state of component wrapped in HOC

10

I have a component wrapped in an HOC. I'm mainly interested in testing the inner component, but I'm finding it difficult to do things like access its state since the root component of the wrapper is the HOC. Do I have to export a separate unwrapped version of the component for testing? What is the justification for not exposing state on non-root components?

matthewgertner picture matthewgertner  ·  1 Jun 2016

Most helpful comment

60

For a decorative HOC (while using react-jss), this is the solution I am using

Edit: I think it would work for decorative syntax too. I haven't tried.

Foo.jsx

import React from 'react';
import injectSheet from 'react-jss';
const styles = ({ color }) => ({
    styled: {
        background: color.bg,
        color: color.fg,
    }
});

class Foo extends React.Component {
    state = {
        foo: 'Foo',
    };

    render() {
        <div className=`foo ${props.classes.styled}`>Foo</div>
    }
}

export injectSheet(styles)(Foo);

Foo.spec.jsx

// import react, enzyme etc
import Foo from 'Elements/Foo';
import { ThemeProvider } from 'react-jss';
const wrapper = mount(
    <ThemeProvider theme={{ color: { bg: '#000000', fg: '#ffffff' } }}>
        <Foo />
    </ThemeProvider>
);
const componentInstance = wrapper
    .childAt(0) // could also be .find(Foo)
    .instance();

// do testing with componentInstance.state.something or componentInstance.props.something
test('something', () => {
    expect(componentInstance.state.foo).toBe('Foo');
});

I hope it helps.

swashata picture swashata  ·  30 Apr 2018

All comments

0

Stumbled on https://github.com/airbnb/enzyme/issues/98. Looks like you are doing the same thing I ended up doing (i.e. exporting the unwrapped version as well). I'll close this but IMO it would be interesting to be able to access the state of the wrapped component since then I would really be testing the same thing that is used in the real app (i.e. the HOC wrapper).

matthewgertner picture matthewgertner  ·  1 Jun 2016
0

I don't think this is something React itself exposes.

ljharb picture ljharb  ·  1 Jun 2016
22

@CosticaPuntaru @matthewgertner this worked for me.
const wrapper = shallow(component, { context: { /* pass context if required */}}); // decorated component
const child = shallow(wrapper.get(0)); // pure component

kokill picture kokill  ·  3 Jun 2016
0

@ljharb Not sure I follow. Isn't state just a normal property of the component?

matthewgertner picture matthewgertner  ·  4 Jun 2016
1

@kokill I need to use mount since my tests rely on lifecycle methods.

matthewgertner picture matthewgertner  ·  4 Jun 2016
0

@matthewgertner yes but of the _wrapper_ component. I don't think you can get at the _wrapped_ component's state.

ljharb picture ljharb  ·  4 Jun 2016
31

It's usually better to do bottom up (leaf nodes to root) instead of top down testing anyways. You should test your presentational (or whatever sits at the bottom of your component tree) components, mocking any props that they require. Then move up the tree and do the same for their parent components.

This is why shallow rendering is so popular. Reaching down into the component tree to validate deep child state is confusing (and difficult as @ljharb mentions). If you validate in isolation at each level then you get an implied contract that the entire tree should render as expected.

For components wrapped in HOCs we usually recommend exporting the unwrapped component and mocking any props that the HOC is providing. If you want to test the HOC itself you are allowed to query props on a child component, which should be enough if you're using composition.

aweary picture aweary  ·  4 Jun 2016
2

Fair enough, that's the approach I ended up taking and it looks good so far. Thanks!

matthewgertner picture matthewgertner  ·  7 Jun 2016
53

I finally found a very good solution to get a wrapper from a decorated component. For shallowed components you can use dive() but there is no similar method for mounted components. This is the solution I did:

const wrapper = mount(shallow(<MyDecoratedComponent />).get(0))

Works like a charm :)

dabit1 picture dabit1  ·  1 Feb 2018
0

I'm having this issue too. I have to wrap the component under test to provide the context:

      wrapper = mount(
        <DragDropContextProvider>
          <MyComponent>
        </DragDropContextProvider>);

MyComponent will not mount if it's not wrapped inside DragDropContextProvider, but I need to get its state to test certain things. That state clearly exists somewhere, because MyComponent is running as expected in the rest of the tests, but I can't get at it.

jstray picture jstray  ·  16 Feb 2018
0

I can confirm that @dabit1 solution works. Unfortunately, in my circumstance, my company requires us to use javascript decorators, so I can't export the base component for testing.

abtrumpet picture abtrumpet  ·  21 Feb 2018
6

Hi @dabit1 , it's not clear to me what <MyComponent /> is in this example.

I have a component that looks something like this in the test:

<Provider store={mockStore}>
  <MyComponent ...props></MyComponent>
</Provider>

I attempted to use what I thought your solution was suggesting:

wrapper = mount(shallow(
<Provider store={mockStore}>
  <MyComponent ...props></MyComponent>
</Provider>
).get(0))

...and wound up with the same error I had before I was wrapping the component with Provider. Am I missing something here?

mgerring picture mgerring  ·  22 Feb 2018
0

@mgerring My solution is for decorated components. Is your component decorated? If not, I think just with mount() should works:

mount(
  <Provider store={mockStore}>
    <MyComponent ...props></MyComponent>
  </Provider>
)

What error do you have exactly?

dabit1 picture dabit1  ·  22 Feb 2018
0

@dabit1 I have the same very issue with the example you show. shallow() cannot be used in my test. How do you access the state of MyComponent?

AntonioRedondo picture AntonioRedondo  ·  27 Apr 2018
0

@AntonioRedondo can you show me your test?

dabit1 picture dabit1  ·  27 Apr 2018
0

It's exactly like the example you put three comments above.

As @mgerring says, it looks like there is no way to get the state of a component when using mount() if it's not the root. And this is an issue when <Provider> is used as root.

If I call .state() on anything apart the root I receive the error: ReactWrapper::state() can only be called on the root. And .dive() can only be called on shallowed rendered components, thing that I cannot use because the test is huge and it would break rest of dozens of tests.

AntonioRedondo picture AntonioRedondo  ·  27 Apr 2018
0

@AntonioRedondo so in my first comment you will find the solution. You have to mount a react element from shallowed component.

dabit1 picture dabit1  ·  27 Apr 2018
60

For a decorative HOC (while using react-jss), this is the solution I am using

Edit: I think it would work for decorative syntax too. I haven't tried.

Foo.jsx

import React from 'react';
import injectSheet from 'react-jss';
const styles = ({ color }) => ({
    styled: {
        background: color.bg,
        color: color.fg,
    }
});

class Foo extends React.Component {
    state = {
        foo: 'Foo',
    };

    render() {
        <div className=`foo ${props.classes.styled}`>Foo</div>
    }
}

export injectSheet(styles)(Foo);

Foo.spec.jsx

// import react, enzyme etc
import Foo from 'Elements/Foo';
import { ThemeProvider } from 'react-jss';
const wrapper = mount(
    <ThemeProvider theme={{ color: { bg: '#000000', fg: '#ffffff' } }}>
        <Foo />
    </ThemeProvider>
);
const componentInstance = wrapper
    .childAt(0) // could also be .find(Foo)
    .instance();

// do testing with componentInstance.state.something or componentInstance.props.something
test('something', () => {
    expect(componentInstance.state.foo).toBe('Foo');
});

I hope it helps.

swashata picture swashata  ·  30 Apr 2018
-1

shallow()

AGMETEOR picture AGMETEOR  ·  14 Nov 2018