Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

enzyme shallowWrapper.setState not working for redux connected components

KINDLY READ BEFORE MARKING IT AS DUPLICATE

So, I went through all the suggested questions and did almost 2 days research on finding out the reason behind my problem.

Here is what I have -

1. A component named SignIn with some local state connected to redux store.

class SignIn extends React.Component {
  constructor(props) {
    super(props);
    this.state = { isLoading: false };
  }
  render () {
    isLoading
    ? <SomeLoadingComponent />
    : <MainSigninComponent />
  }
}
export default ConnectedSignIn = connect(mapStateToProps)(SignIn);

Now as you can see the render output of SignIn changes with the change in local state and I intend to snapshot test both the output.

So I wrote two test cases.

// This one is alright as it test on the default state and renders the actual SigninComponent.
test(`it renders correctly`, () => {
  const wrapper = shallow(<ConnectedSignIn {...props} />, { context: { store }});

  // .dive() because I intend to snapshot my actual SignIn component and not the connect wrapper.
  expect(wrapper.dive()).toMatchSnapshot();
});

Now when I intend to change the state to { isLoading: true } I fire a call to setState like this in second test.

test(`it renders the loading view on setting isLoading state to true`, () => {
  const wrapper = shallow(<ConnectedSignIn {...props} />, { context: { store }});

  console.log(wrapper.dive().state()) // returns { isLoading: false }

  // Again .dive because I want to call setState on my SignIn component

  wrapper.dive().setState({ isLoading: true });
  console.log(wrapper.dive().state()) // still returns { isLoading: false }

  // Tried the callback method to ensure async op completion
  wrapper.dive().setState({ isLoading: true }, () => {
    console.log(wrapper.dive().state()) // still returns { isLoading: false }
  });
});

So going by above code and output, I infer that the shallow wrapper's setState is not working properly, as in not actually updating the state of component.

Just to mention,

1. I also tried using wrapper.dive().instance().setState() as I read it in some question that to update the state of instance, this way would be better. Did not work.

2. I also tried forcing update on shallowed component using wrapper.update() and wrapper.dive().update(). This one also didn't work.

My dependency versions

"react": "16.0.0",

"react-native": "0.50.0",

"react-redux": "^5.0.6",

"enzyme": "^3.3.0",

"enzyme-adapter-react-16": "^1.1.1",

"jest": "21.2.1",

"react-dom": "^16.2.0"

I have already read guides on testing components in isolation by separating them from redux. I did that and my test cases ran fine but I wish to know whether this behaviour is normal or a bug and if someone has been successful in testing state changes as well as render changes of a redux connected component then kindly let me know your approach.

like image 552
Himanshu Singh Avatar asked Oct 14 '25 03:10

Himanshu Singh


2 Answers

Every time you use dive() it creates a new wrapper, so all of your calls are referencing a new wrapper rather than the one you updated. Instead, you could try this:

test(`it renders the loading view on setting isLoading state to true, () => {
  const wrapper = shallow(<ConnectedSignIn {...props} />, { context: { store }});
  const component = wrapper.dive()

  component.setState({ isLoading: true });
  console.log(component.state()) // should be {isLoading: true}
});

For reference, see Jordan Harband's answer to this Enzyme issue.

like image 67
Christian M. Macy Avatar answered Oct 17 '25 01:10

Christian M. Macy


This is bad practice. You should test SignIn and ConnectedSignIn independently.

1. Create a named export.

// SignIn.js

export class SignIn extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      isLoading: false
    };
  }

  render() {
    const { isLoading } = this.state;

    return (
      (isLoading) ? <SomeLoadingComponent /> : <MainSigninComponent />
    );
  }
}

export default ConnectedSignIn = connect(mapStateToProps)(SignIn);

2. Import the SignIn component using the following syntax.

import { SignIn } from 'SignIn.js'

Notice we import SignIn instead of ConnectedSignIn.

like image 20
Pawan Gangwani Avatar answered Oct 17 '25 01:10

Pawan Gangwani