-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Comments
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 |
That's true, but I think you have to tag on another |
yes if you have 3 HOCs it becomes the rather terrible const wrapper = shallow(<Foo />).first().shallow().first().shallow().first().shallow(); |
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. |
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 Edit: I think the find approach might have a similar issue actually... |
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 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. |
@aweary I think your suggestion is similar to my thought about adding an |
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. |
Ideally we'd want both options I think: "only expand through these" or "expand unless it's one of these". |
re #539 (comment) - specifically, as a separate API from |
I second @ljharb on both points: both options and no depth number |
For anyone looking for a temporary solution, we are using an cc @matthieuprat for the credit.
|
Here is a Gist with a bunch of tests for this |
Here's a little helper that I use. Works for multiple nesting too (multiple decorators / HoC wrapping).
And then:
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. |
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 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); |
Linking to #250. |
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. |
My opinion on this:
Honestly you've changed your design, your Component Under test is now doing more. It's no longer as simple. So you should expect your tests to break in this case. It's par for the course. When you change your design by introducing an HOC, there's no magic that's going to allow your tests from breaking. Your tests are designed to give you feedback. This is not too much fragility as it is you've changed your contract in a big way inside that component. So it's totally expected if you introduce an HOC like that. When I TDD I get that kind of feedback early. And it forces me to think a little more about my design up front in baby steps as I go. I expect this kind of feedback from shallow. Instead of looking for a solution that prevents your tests from breaking and more magic to get around it, think more about how your'e designing your components and when it's natural to expect your tests to break when you completely redesign the contract like that. There's no such thing as "all fragile or all not". You can have a mix of fragility, with expected contracts breaking your tests, both are different cases...so know when changing your design, you're actually getting the feedback you should be getting. If you test after well, it's kinda too late, now you have to try to go and break apart your components to try and make them simpler. That's exactly why I TDD. I don't end up in that situation that much...because I move things around and break things apart early. I know this is a late reply, but I'm just looking through old issues in here. Thought to comment. |
Yes but the new HOC is just an implementation detail. The interface of your component did not change so the unit tests for it should not have to change. |
Yea I just don't think it's a big deal in those cases honestly personally plus you can use a recursive dive() helper method to solve this. But regardless I think if you're doing dive() lot, you have to wonder if maybe you're also actually over using HOCs (yes you can over use it, there are other ways to decouple) and I'm glad hooks are coming out with React as well now. Adding HOCs is actually making your component do more, and more complex. There are ways to break things apart and decouple without having to nest a bunch of HOCs or continuously keep adding HOCs often IMO. I agree with FB that HOCs and render props are really kind of a bad design pattern which is what they admitted in their hooks docs. HOCs are a form of injection but you end up making your component more complex, instead of adhereing to the 4 rules of simple design. It's a design feedback actually IMO with dive/shallow. |
@lencioni can you site what you mean by the following for a shallow until helper:
plus you'd want that to dive() not shallow for a shallowUntil |
Oh boy I wrote that a long time ago and before we added dive. I don't remember exactly what I meant, but maybe I was thinking about the possibility that Enzyme could be configured to do this automatically so when you introduce or remove an HOC, you don't need to change a bunch of tests. Although, that certainly has its own set of tradeoffs and that probably would be worse in a number of other ways. |
I'm into something similar here:
But when
which is frankly awful... I honestly don't know if enzyme provides a nicer way of doing this... Maybe I'm missing something. |
@dalvallana you either have to do that, or to export the wrapped component directly - neither of which is nice. In that specific case, however, you should be able to shallow render the HOC, and before calling |
I wish this was solved in Enzyme instead of there being a potpourri of snippets, gists and unmaintained one-off packages to choose from. Not having official support suggests that there's something unsound about diving based on a selector, but this thread doesn't really explicate what the issues would be. |
@slikts typically you'd use one |
It's not required in the sense that there is a workaround (chaining |
@slikts I think the root of the problem is that Enzyme provides too many utilities that are dependent on the structure of the component tree rather than something closer to what the user is seeing. This is the reason why I am moving away from Enzyme and towards |
@slikts yes, that is true, but your tests for the component are testing some of the implementation details, and that's ok. @goldenshun good testing is a balance of surface testing (like what RTL and |
This comment has been minimized.
This comment has been minimized.
@ljharb <https://github.com/ljharb> The entire premise of this thread is
that using shallow with higher order components is brittle. Mocks can be
brittle too but in this case they are more stable than shallow.
…On Fri, Apr 12, 2019, 11:19 PM Sitian Liu ***@***.***> wrote:
@ljharb <https://github.com/ljharb> I think you meant to tag @goldenshun
<https://github.com/goldenshun>. no worries.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#539 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAWY34NBXORCBQEC7K4BBALPQFUP5ANCNFSM4CMM3SWA>
.
|
@goldenshun i suppose that’s subjective; i don’t find that to be the case. Certainly there is an improvement to be made here, but the brittleness you’re referring to is “you have to match the count of HOCs with the count of dives”. In practice, this is only mildly annoying - the brittleness of mocks is that your code will fail in production but your tests won’t show that, because the mocks aren’t up to date and mask the errors. |
@ljharb Your criticism of mocks is true for both jest mocks and shallow
rendering. They are both forms of mocking because they both halt rendering
of the component tree at a certain later of the component tree. As with any
unit test, they will not detect all classes of error.
My submission is that jest mocks are a more reliable mechanism for the case
at hand because they will be more resilient to changes in the component
structure.
…On Sat, Apr 13, 2019, 8:59 AM Jordan Harband ***@***.***> wrote:
@goldenshun <https://github.com/goldenshun> i suppose that’s subjective;
i don’t find that to be the case.
Certainly there is an improvement to be made here, but the brittleness
you’re referring to is “you have to match the count of HOCs with the count
of dives”. In practice, this is only mildly annoying - the brittleness of
mocks is that your code will fail in production but your tests won’t show
that, because the mocks aren’t up to date and mask the errors.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#539 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAWY34NXCKPY2RHPP4CTSYTPQHYNVANCNFSM4CMM3SWA>
.
|
In practice, I’ve found the opposite to be true - and while changing the component structure shouldn’t change tests for other things, the tests for the component being changed shouldn’t necessarily be completely resilient to changes in structure. |
This thread drives me ape shit. |
@goldensunliu this is exactly why I hate Apollo, and is a typical example of what I've ranted about...a Your premise that Enzyme doesn't help here is wrong, it's that Apollo's HOC nesting is shit. Essentially Apollo code is the imperative shell, and you shouldn't mix functional core logic inside all those nested HOCs. Boundaries by Gary Bernhardt (this says Ruby but we use the concept in ANY code, any lang so understand what he's saying here and apply that to your React Design) So in the case of Apollo test the imperative shell is the Apollo Nested HOCs garbage that you think is "making your life easier" but really it's causing you hell because it's poor design and therefore not easily testable. Decouple your business behavior (user handler logic, container logic) out of there and move them into their own layers, keep the methods you move out small, components small, and test them with unit tests with shallow or if they are just pure functions without React stuff, test them with simple unit tests using Jest or Mocha. |
I have a class component called
If I do:
And then:
I get:
Is there something I can do test this, or do I need to use Thanks for any help. |
@hmmChase use the new |
Hi, any update on this ? are there plans to implement something like shallowuntil or like list of nodes which are to be expanded or not expanded, as discussed ? |
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.
Of course, you can also export the unwrapped component as a named export, and use that to find by reference as well.
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.
expand
option toshallow
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.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.depth
option toshallow
, 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.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.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?
The text was updated successfully, but these errors were encountered: