Hi,
I'm having trouble with a specific test scenario in my React + redux application. I want to test that a change to the redux store is correctly reflected in my component. For that, in my test I create a shallow component, connected to redux, and then I update the store using an action, then verify that the mounted component has the correct data. Which it doesn't.
Here's a test case to reproduce this:
import * as React from 'react';
import { connect } from 'react-redux';
import { createStore } from 'redux';
import { shallow } from 'enzyme';
import sinon from 'sinon';
const actions = {
// This would fetch a resource on the network.
getList: () => {},
receiveList: list => {
return {
type: 'RECEIVE_LIST',
list,
};
},
// This would fetch a list of resources on the network.
getObjects: () => {},
};
const reducer = (state = { list: [] }, action) => {
switch (action.type) {
case 'RECEIVE_LIST':
return { ...state, list: action.list };
default:
return state;
}
};
class MyAppBase extends React.Component {
componentDidMount() {
this.props.dispatch(actions.getList());
}
componentDidUpdate(nextProps) {
if (nextProps.list !== this.props.list) {
this.props.dispatch(actions.getObjects(nextProps.list));
}
}
render() {
return null;
}
}
const MyApp = connect(state => { return { list: state.list }; })(MyAppBase);
describe('<MyApp>', () => {
it('fetches objects once list is received', () => {
// Mock actions that call the network.
sinon.stub(actions, 'getList');
actions.getList.returns({type: 'whatever'});
sinon.stub(actions, 'getObjects');
actions.getObjects.returns({type: 'whatever'});
// Create a redux store.
const store = createStore(reducer);
// Create the component.
const wrapper = shallow(<MyApp store={store} />).dive();
// Verify that `componentDidMount` was called but not `componentDidUpdate`.
expect(actions.getList.callCount).toEqual(1);
expect(actions.getObjects.callCount).toEqual(0);
// Dispatch an action that will change the redux store.
store.dispatch(actions.receiveList([ 'aa', 'bb' ]));
wrapper.update();
// Expect the `componentDidUpdate` method to be called.
expect(actions.getObjects.callCount).toEqual(1);
// This doesn't work, for some reason `componentDidUpdate` is never called.
});
});
My component's componentDidUpdate method is never called, and thus I cannot verify that my component has the correct data.
componentDidUpdate is called with the state from the redux store.
| library | version
| ------------------- | -------
| enzyme | 3.7.0
| react | 16.6.0
| react-dom | 16.6.0
| react-test-renderer | n/a
| adapter (below) | 1.6.0
Thanks!
What version of react-redux are you using? v6 uses new Context, which enzyme does not yet support.
I'm using "react-redux": "5.1.0".
I think the issue is that .dive() doesn't currently bring context along correctly; this should be fixed in the next release.
Hi @ljharb, I've tried with the latest release (enzyme=3.9.0, enzyme-adapter-react-16=1.11.2) to no success. Was this expected to be fixed with that release? I've used the exact same test case I put in my first comment.
Thanks!
@adngdb yes, i'd expect it to be. if there's any way to make a minimal test case without redux, that would make it much easier to fix for sure :-)
Just to confirm that this happens without any use of Redux either - though I am using getDerivedStateFromProps - put a quick console.log in the constructor, getDerivedStateFromProps, and componentDidUpdate, and the last one doesn't get called.
class MyAppBase extends React.Component {
static getDerivedStateFromProps() {
console.log("getDerivedStateFromProps called");
return { random: Math.random() }; // Force it to actually change state every time
}
constructor(props) {
super(props);
console.log("constructor called");
}
componentDidUpdate() {
console.log("componentDidUpdate called");
}
render() {
return null;
}
}
It is still in a Redux app, but the tests I'm running are against the Component that is not connected to Redux as in the original example.
+1 to last comment.
I can also reproduce this with only getDerivedStateFromProps, no redux needed.
I think this is also already fixed in master, as I can't reproduce it from master, only in 3.9.0.
Given that ^ I'm going to close this; the next release of enzyme should fix it.
It's great that this fix is in master, but when will it be released? Given your package monolith, I have to jump through some serious hoops to make this work right now.
I think the getDerivedStateFromProps maybe be fixed as per https://github.com/airbnb/enzyme/issues/2009#issuecomment-484969410 but I'm not sure that the original issue is fixed on master.
The last expect of this example still fails for me with enzyme @ da5320719ba5416e33b37ef4c011a9bf3eeea87c and the enzyme-adapter-react-16 @ 1.13.0.
Note that this is using react-redux @ v5, so perhaps it's only a problem with legacy context. I have not had a chance to test further yet.
import * as React from 'react';
import { connect } from 'react-redux';
import { createStore } from 'redux';
import { shallow } from 'enzyme';
const reducer = (state = { count: 0 }, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
};
class MyComponent extends React.Component {
render() {
return <div>{this.props.count}</div>;
}
}
const MyConnectedComponent = connect((s) => s)(MyComponent);
describe('shallow mounted, connected component', () => {
it('updates the wrapper returned from dive when the store changes', () => {
const store = createStore(reducer);
const wrapper = shallow(<MyConnectedComponent store={store} />).dive();
expect(wrapper.text()).toEqual('0');
store.dispatch({ type: 'increment' });
expect(store.getState().count).toEqual(1);
expect(wrapper.text()).toEqual('1');
});
});
@tilgovi if latest master isn't fixing your issue, could you send a PR with a test case, ideally not using react-redux? That should help me quickly find a solution.
v3.10.0 has now been released.