New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve shallow rendering for use with higher-order components #539

Open
lencioni opened this Issue Aug 12, 2016 · 17 comments

Comments

Projects
None yet
10 participants
@lencioni
Member

lencioni commented Aug 12, 2016

When using shallow rendering with a component that is wrapped by a higher order component, you need to take some extra steps to make shallow rendering give you the structure you expect. One way is to shallow render the wrapped component, find the unwrapped component within this structure, and then shallow render that. e.g.

const wrapper = shallow(<Foo />).find('Foo').shallow();

Of course, you can also export the unwrapped component as a named export, and use that to find by reference as well.

import Foo, { Foo as UnwrappedFoo } from './Foo';

...

const wrapper = shallow(<Foo />).find(UnwrappedFoo).shallow();

This bums me out a little, because if you have a bunch of tests for a component, and then later decide to wrap that component in a HOC, you need to go back and update all of your tests in this way. Using the string approach feels less than ideal, and the reference approach feels a bit cumbersome when applied on a large scale. I'm wondering if we can come up with a solution that will work well for this scenario. Here are some thoughts.

  • In #250, @lelandrichardson proposed "Mixed-depth shallow rendering mode", which would allow you to pass an expand option to shallow with references to components to "expand" the shallow render tree into. I think this would work if your component is only wrapped by a single HOC, but if you wrap it in multiple HOCs, you end up with the same problem again, because you have multiple levels that you need to expand, but only a reference to the top level.
  • We could add an until option to shallow that would take a reference to a component or a string reference. This would reduce some of the boilerplate of .find('Foo').shallow() just a little, but I think it has many of the same disadvantages that we currently have.
  • Another possibility is to provide a depth option to shallow, that would take a number of components to drill down into. I think that this would be more comfortable than what we currently do, but it still doesn't fully solve the problem--you still have to know to add this option to every place you want to use shallow rendering with wrapped components. And, if you wrap a component in another HOC, you need to increment all of the values.
  • We could add a static property on HOCs that signify that they are HOCs. Enzyme could then look for this static property when shallow rendering, and dig deeper. And, if you find yourself in a situation where you want to avoid this behavior, we could add an option to shallow to opt-out, but maybe that's YAGNI. The downside is that this would only work for HOCs that opt in to this behavior, so it likely wouldn't solve the problem when using third-party HOCs--unless it somehow caught on and spread to all corners.
  • What if we allowed people to register a list of regexes used to expand components when shallow rendering, matching on their names. Using this, folks could configure their testing environment to do what they want, and then not have to think about it much any more. At Airbnb, we use a naming convention of functionName(ComponentName) for HOCs which we could configure all be automatically expanded with a regex like /\([^\)]+\)/. I like this option because I think it would solve this issue for us with little configuration, but I dislike it because it makes shallow rendering somewhat magical and might cause unexpected results.

Do any of these sound good? Should we employ multiple strategies? Are there other possibilities?

@nfcampos

This comment has been minimized.

Collaborator

nfcampos commented Aug 12, 2016

You can also do

const wrapper = shallow(<Foo />).first().shallow();

I definitely feel this pain, but I'm not sure what the right solution is. But I do lean more towards defining the depth

@lencioni

This comment has been minimized.

Member

lencioni commented Aug 12, 2016

That's true, but I think you have to tag on another .first().shallow() for each HOC.

@nfcampos

This comment has been minimized.

Collaborator

nfcampos commented Aug 12, 2016

yes if you have 3 HOCs it becomes the rather terrible

const wrapper = shallow(<Foo />).first().shallow().first().shallow().first().shallow();
@ljharb

This comment has been minimized.

Member

ljharb commented Aug 12, 2016

The only option I like there is #250. Even with multiple HOCs, I think that would work transparently. You'd still always need to export the pure version, but I think that's just an inherent limitation in HOCs that enzyme can't gracefully overcome.

@lencioni

This comment has been minimized.

Member

lencioni commented Aug 12, 2016

If I understand that proposal correctly, you would need to export each level of HOC that you wrap your component in, and then pass those all as references to the expand option. I think I would rather use .find('Foo').shallow().

Edit: I think the find approach might have a similar issue actually...

@aweary

This comment has been minimized.

Collaborator

aweary commented Aug 12, 2016

Ideally, I think it would be nice to have an API would be able to find a component at any level in the potential render tree for a component, even if it was too deep to be rendered with a shallow render.

var deepWrapper = wrapper.shallowFromDeepChild('SomeDeepComponent');

Though I think that would be difficult to implement. I like #250 but if you have to export every level of HOC then it doesn't seem to bring any advantage over just testing the exported component directly. It's definitely a hard problem.

@lencioni

This comment has been minimized.

Member

lencioni commented Aug 12, 2016

@aweary I think your suggestion is similar to my thought about adding an until option, which would keep going until it rendered something that matched the selector.

@CurtisHumphrey

This comment has been minimized.

Contributor

CurtisHumphrey commented Aug 15, 2016

I suggest instead of a list to expand have a list of do not expand. For me and our tests, most of the time we use shallow because we have components inside of it we do not want to expand (big things maybe directly connect to redux etc). We know this list as we build the widget and tests. Then if we later wrap it with HOC that list doesn't change and nothing in the tests have to change, problem solved.

@ljharb

This comment has been minimized.

Member

ljharb commented Aug 15, 2016

Ideally we'd want both options I think: "only expand through these" or "expand unless it's one of these".

@ljharb

This comment has been minimized.

Member

ljharb commented Aug 22, 2016

re #539 (comment) - specifically, as a separate API from shallow - and I don't think we should allow any sort of "depth" number, since that tightly couples tests to JSX structure.

@CurtisHumphrey

This comment has been minimized.

Contributor

CurtisHumphrey commented Sep 7, 2016

I second @ljharb on both points: both options and no depth number

@oliviertassinari

This comment has been minimized.

oliviertassinari commented Nov 10, 2016

For anyone looking for a temporary solution, we are using an until operator at @doctolib as we use quite some HoC:

cc @matthieuprat for the credit.

enzyme/operator/until.js

export default function until(selector, {context} = this.options) {
  if (this.isEmptyRender() || typeof this.node.type === 'string')
    return this;

  const instance = this.instance();
  if (instance.getChildContext) {
    context = {
      ...context,
      ...instance.getChildContext(),
    };
  }

  return this.is(selector)
    ? this.shallow({context})
    : this.shallow({context})::until(selector, {context});
}

That we use it like this:

const AppBanner = ({banner, children}) => {
  return (
    <Banner>
      {children}
    </Banner>
  );
};

export default compose(
  pure,
  connect(({
    globalMessagesBanner$,
  }) => ({
    banner: globalMessagesBanner$.startWith('key: common.error'),
  })),
)(AppBanner);
const wrapper = shallow(<AppBanner />, {context});

assert.strictEqual(
  wrapper.until('[banner]').find(Banner).props().children,
  'key: common.error'
);
@matthieuprat

This comment has been minimized.

matthieuprat commented Nov 10, 2016

Here is a Gist with a bunch of tests for this until operator to make it more "graspable": https://gist.github.com/matthieuprat/5fd37abbd4a4002e6cfe0c73ae54cda8

@elodszopos

This comment has been minimized.

elodszopos commented Dec 28, 2016

Here's a little helper that I use. Works for multiple nesting too (multiple decorators / HoC wrapping).

export function renderComponent(Component, defaultProps = {}) {
  return function innerRender(props = {}, nestLevel) {
    let rendered = shallow(<Component {...defaultProps} {...props} />);

    if (nestLevel) {
      let repeat = nestLevel;

      while (repeat--) {
        rendered = rendered.first().shallow();
      }
    }

    return rendered;
  };
}

And then:

const renderer = renderComponent(Foo); // defined in describe
const wrapper = renderer({ someProp: someValue }, 1); // @connected Foo, to be used in an it block

It's a temporary solution really. I'd like to fully support this effort and if there's anything I can help with, I will.

I'm also all for what @ljharb said in his comment above. The temporary solution that I have tightly couples to JSX and is volatile. Far from ideal.

@kumar303

This comment has been minimized.

kumar303 commented Aug 10, 2017

EDIT: Here is a more detailed explanation of this approach: https://hacks.mozilla.org/2018/04/testing-strategies-for-react-and-redux/

This is a helper that is working for me so far (here is the current code and tests). It's nice because you can wrap as many HOCs around your component as you need to without having to update any test code. It's pretty similar to @oliviertassinari's approach but takes a component class instead of a selector.

import { oneLine } from 'common-tags';
import { shallow } from 'enzyme';

/*
 * Repeatedly render a component tree using enzyme.shallow() until
 * finding and rendering TargetComponent.
 *
 * The `componentInstance` parameter is a React component instance.
 * Example: <MyComponent {...props} />
 *
 * The `TargetComponent` parameter is the React class (or function) that
 * you want to retrieve from the component tree.
 */
export function shallowUntilTarget(componentInstance, TargetComponent, {
  maxTries = 10,
  shallowOptions,
  _shallow = shallow,
} = {}) {
  if (!componentInstance) {
    throw new Error('componentInstance parameter is required');
  }
  if (!TargetComponent) {
    throw new Error('TargetComponent parameter is required');
  }

  let root = _shallow(componentInstance, shallowOptions);

  if (typeof root.type() === 'string') {
    // If type() is a string then it's a DOM Node.
    // If it were wrapped, it would be a React component.
    throw new Error(
      'Cannot unwrap this component because it is not wrapped');
  }

  for (let tries = 1; tries <= maxTries; tries++) {
    if (root.is(TargetComponent)) {
      // Now that we found the target component, render it.
      return root.shallow(shallowOptions);
    }
    // Unwrap the next component in the hierarchy.
    root = root.dive();
  }

  throw new Error(oneLine`Could not find ${TargetComponent} in rendered
    instance: ${componentInstance}; gave up after ${maxTries} tries`
  );
}

Here is an example of how I use it. In SomeComponent.js:

import React from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';

export function SomeComponentBase() {
  return <div>This is the unwrapped component that we want to test</div>;
}

// Pretend that this is a typical Redux mapper.
const mapStateToProps = (state) => ({});

export default compose(
  connect(mapStateToProps),
)(SomeComponentBase);

Example of rendering it for a test:

import SomeComponent, { SomeComponentBase } from './SomeComponent';
import { shallowUntilTarget } from './utils';

// This unwraps SomeComponent until it finds SomeComponentBase:
const root = shallowUntilTarget(<SomeComponent />, SomeComponentBase);
@ljharb

This comment has been minimized.

Member

ljharb commented Sep 26, 2017

Linking to #250.

@coryhouse coryhouse referenced this issue Apr 27, 2018

Open

Automated Testing #6

0 of 27 tasks complete
@goldenshun

This comment has been minimized.

goldenshun commented Jul 3, 2018

I wasn't able to get the "shallow until render" approaches to work, so went with an approach that mocks the specific component I want to avoid rendering. Jest mocks are a little quirky, but they did the job. Especially helpful when testing Apollo's Query/graphql components.

https://medium.com/@sconnolly17/testing-graphql-container-components-with-react-apollo-and-jest-9706a00b4aeb

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment