Enzyme: Impossible to use "setProps" when using Context API with HOC Component

Created on 5 Oct 2018  路  12Comments  路  Source: enzymejs/enzyme

Describe the bug
I have a component that exports using HOC:

const Component = (props) => (
...
);

export default withLanguage(PaymentMethodItem);

The function "withLanguage" implements a Consumer. In my tests with Enzyme I need to provide a Provider on mount, like this:

wrapper = mount((
   <LanguageContext.Provider value={i18n}>
        <Component onPress={() => null} />
   </LanguageContext.Provider>
));

It's impossible to setProps to the Component. There's no way to that. Neither using find, or anything:

// NOT WORKING. Can only setProps on root component!!
component.find(Component).setProps({ onPress: () => console.log('hello world') })

Expected behavior
There must be a way to use setProps with Context API components.

Most helpful comment

I've worked around this by doing something like this:

// Write a wrapper around the component to test
function ComponentWithContext(props) {
  return (
    <LanguageContext.Provider value={i18n}>
      <Component {...props} />{/* pass all props to the component being tested */}
    </LanguageContext.Provider>
  );
}

const wrapper = mount(
  <ComponentWithContext onPress={() => null} />
);

// ComponentWithContext will pass the changed props to Component
wrapper.setProps({ onPress: () => alert('I was pressed') });

It's not perfect. For example,

wrapper.type() === Component; // false
wrapper.type() === ComponentWithContext; // true

But you can always

wrapper.find(Component)

to get your _actual_ component.

All 12 comments

enzyme doesn't yet support new Context.

Is there any PR that address this? maybe it can be a possible contribution?

I think I found a way to work with the children component:

  1. Mount your component
  2. .find('your children') it this will return a React Wrapper
  3. .instance() this will return a React Component. I need it to check the State, so this was my solution. Let me know how does it work with props.

This last part will let you investigate the state I asume you can also set props to it

I've worked around this by doing something like this:

// Write a wrapper around the component to test
function ComponentWithContext(props) {
  return (
    <LanguageContext.Provider value={i18n}>
      <Component {...props} />{/* pass all props to the component being tested */}
    </LanguageContext.Provider>
  );
}

const wrapper = mount(
  <ComponentWithContext onPress={() => null} />
);

// ComponentWithContext will pass the changed props to Component
wrapper.setProps({ onPress: () => alert('I was pressed') });

It's not perfect. For example,

wrapper.type() === Component; // false
wrapper.type() === ComponentWithContext; // true

But you can always

wrapper.find(Component)

to get your _actual_ component.

P.S. I created a library that could be of some use to you if you're dealing with a lot of context in your app. It's called enzyme-context and it allows you to create mount/shallow functions that are specific to _your_ application and that provide the components you mount with the context they need to render. The specialized mount/shallow functions are configurable via plugins so it would be very straightforward to create a plugin that automatically applies the workaround from my previous comment.

@minznerjosh thanks for a workaround. However, it won't work if you want to use .setState():

Error: ReactWrapper::setState() can only be called on the root

setState() is allowed on non-roots in the latest version of enzyme.

They appear to be! Wanna open a PR? 馃槉

@minznerjosh Let me know if you have comments I can add them.

https://github.com/airbnb/enzyme/pull/2010

Had the similar problem recently so here's my work-around in case it's useful to anyone.

Here I have a BabylonScene component which provides the context for a Camera component but which needs props to be provided to it.

// By using this as a WrappingComponent we can use standard Enzyme wrappers to test a component which needs context
const TestBabylonScene = (props: any) => {
  return <BabylonScene config="Context provider can be configured here" />;
};

test("Name changes are reflected in internalCamera", () => {
  const wrapper = mount<Camera>(<Camera name="TestCamera" />, {
    wrappingComponent: TestBabylonScene
  });

  wrapper.setProps({ name: "RenamedCamera" });

  const camera: Camera = wrapper.instance();
  expect(camera.internalCamera.name).toEqual("RenamedCamera");
});

Closing, since the wrappingComponent should handle this.

Please file new issues if anyone's still having trouble.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mattkauffman23 picture mattkauffman23  路  3Comments

abe903 picture abe903  路  3Comments

amcmillan01 picture amcmillan01  路  3Comments

heikkimu picture heikkimu  路  3Comments

potapovDim picture potapovDim  路  3Comments