17

I have a test which is setting props, to observe some changes in the component. The only complication is that I'm wrapping the rendered element in a <Provider> because there are some connected components further down the tree.

I'm rendering via

const el = () => <MyComponent prop1={ prop1 } />;
const wrapper = mount(<Provider store={store}>{ el() }</Provider>);

I'm then trying to observe some changes by using the following:

wrapper.setProps({ /* new props */ });
// expect()s etc.

The problem is that setProps() is not setting the props properly on the wrapped component. I assume that this is because <Provider> is not actually passing props through as it's not an HoC. Is there a better way to test this than just changing the locally scoped prop variables and re-rendering?

GTF
  • 8,031
  • 5
  • 36
  • 59
  • 2
    For now, I've solved the problem (although not the question) by passing down context via `mount(el, { context, childContextTypes })` instead of wrapping with ``. – GTF Feb 28 '17 at 12:32
  • I have the exact same question. I like to see your solution that worked for you using childContextTypes. Do you mind sharing the code? – Gaurang Patel Feb 04 '19 at 01:14
  • Basically you just need to set the store in the context, e.g. using [`redux-mock-store`](https://github.com/dmitry-zaets/redux-mock-store). i.e. `mount(el, { context: { store }, childContextTypes: { store: PropTypes.object.isRequired }});`. – GTF Feb 04 '19 at 14:31
  • Thanks much for reply @GTF. Later, I was able to follow other examples on internet using context and childContextTypes. Exactly how you described now. Awesome.! – Gaurang Patel Feb 05 '19 at 23:34

5 Answers5

10

Here's an approach using the setProps

import { Provider } from 'react-redux';


const renderComponent = properties => mount(
  React.createElement(
    props => (
      <Provider store={store}>
        <IntlProvider locale="en" defaultLocale="en" messages={messages}>
      <Router>
        <MyComponent {...props} />
      </Router>

      </Provider>
    ),
    properties))

Then in your test

it('should set some props', () => {
   const renderedComponent = renderComponent(props);
   renderedComponent.setProps({ /* some props */ } });

  expect(true).toBe(true);
})
soupette
  • 1,260
  • 11
  • 11
2

Another approach will be to use wrappingComponent.

For example, let say this is your provider

const ExampleProvider = ({ children }) => (
      <Provider store={store}>
        {children}
      </Provider>
    );

and then initialize wrapper as follows

wrapper = mount(<Component />, { wrappingComponent: ExampleProvider});

And then in test cases, you should be able to call wrapper.setProps directly.

Vikas Kumar
  • 2,978
  • 3
  • 14
  • 24
  • 1
    This is the correct way to add a wrapping component, such as a Redux Provider or React Router, to a child component mounted with enzyme. Thank you! – Ruben Martinez Jr. Oct 29 '21 at 16:40
1

You should only be calling setProps on the wrapped component or parent.

A good rule of thumb is your test should only be testing a single component (the parent), so setting props on children is not permitted with enzyme.

https://github.com/airbnb/enzyme/blob/master/docs/api/ShallowWrapper/setProps.md#setpropsnextprops--self

If you have child components further down that we need to satisfy store dependencies for (via Provider and context), then that is fine, but those child components should really have their own isolated tests.

That is where you'd be encouraged to write a test for a change on setProps.

If you find yourself writing tests for a container or connector, you would really only want to verify that the child component is receiving the correct props and or state. For example:

import { createMockStore } from 'mocks'
import { shallwo } from 'enzyme'
// this component exports the redux connection
import Container from '../container'

const state = { foo: 'bar' }

let wrapper
let wrapped
let store

beforeEach(() => {
  store = createMockStore(state)
  wrapper = shallow(<Container store={store} />)
  wrapped = wrapper.find('MyChildComponent')
})

it('props.foo', () => {
  const expected = 'bar'
  const actual = wrapped.prop('foo')
  expect(expected).toEqual(actual)
})

One other tip is that it helps to understand the difference between shallow and mount so you aren't needlessly mocking dependencies for children in a test, the top answer here is a good read:

When should you use render and shallow in Enzyme / React tests?

random-forest-cat
  • 33,652
  • 11
  • 120
  • 99
  • 2
    This does not answer the question, sometimes you want to test with mount. If you are using redux you should be able to test your inner component and test how it manages with prop changes, props that are not delivered from the store. – TacoEater Dec 20 '18 at 19:51
1

I had the same issue after wrapping my component in a Provider. What I did is, I used setProps on children instead of a component itself.

This is the example of my component in test suite:

 let component, connectedComponent; 
 component = () => {
 store = configureMockStore()(myStore);  
 connectedComponent = mount(
     <Provider >
       <MyComponent store={store} params={{xyz: 123}} />
     </Provider>);};

But in the test itself, I did this:

connectedComponent.setProps({children: <MyComponent params={{}} />});
Mina Djuric
  • 520
  • 5
  • 7
0

I would like to provide a similar solution to the one above.

With the mount wrapper as defined in the question,

const wrapper = mount(<Provider store={store}>{ el() }</Provider>);

And then, on the test itself, you can simply clone the children, which is the child component within Provider, and then call setProps:

it('should get do x and y', () => {  
  wrapper.setProps({
    children: cloneElement(wrapper.props().children as ReactElement, { ...props }),
  });

  // handle the rest below
});

If you are using JavaScript, there is no need to include the as ReactElement type assertion.

For those who are using TypeScript, it is necessary to assert it as ReactElement, as ReactChild could be of types ReactElement<any> | ReactText. Since we are certain that the element rendered within Provider is a ReactElement, the fastest solution would be to use type assertion.

wentjun
  • 40,384
  • 10
  • 95
  • 107