enzyme: Documentation does not make it clear when to use `.dive()` vs `.shallow()`
Describe the bug
It’s not clear to me what the differences between .dive()
and .shallow()
are. From the documentation, and the code, it looks like they do the same thing except that .dive()
errors if the element is anything but a component. It also looks like .dive()
inherits the options of the container it is called on, where .shallow()
does not.
Why would one choose to use .dive()
over .shallow()
, or vice versa? I imagine, it .dive()
is preferred over .shallow()
, since it’s more restrictive, but it would be nice if the documentation called that out explicitly.
About this issue
- Original URL
- State: open
- Created 6 years ago
- Reactions: 25
- Comments: 26 (10 by maintainers)
Commits related to this issue
- [Docs] Add a NOTE line in docs of `.dive()` about it would throw an error under some cases. https://github.com/airbnb/enzyme/issues/1798 — committed to markselby9/enzyme by markselby9 6 years ago
- [Docs] Add a NOTE line in docs of `.dive()` about it would throw an error under some cases. https://github.com/airbnb/enzyme/issues/1798 — committed to markselby9/enzyme by markselby9 6 years ago
- [Docs] Add a NOTE line in docs of `.dive()` about it would throw an error under some cases. https://github.com/airbnb/enzyme/issues/1798 — committed to markselby9/enzyme by markselby9 6 years ago
- [Docs] Add a NOTE line in docs of `.dive()` about it would throw an error under some cases. See #1798 — committed to markselby9/enzyme by markselby9 6 years ago
i don’t always have time to get to questions right away; but deleting them is a pretty good way to ensure i can’t answer them :-p
In particular, the mantra i often use is “when shallow rendering, one .dive() per HOC”
.dive()
is sugar for “throw if there’s more than one child, throw if that child isn’t a custom component, call.shallow
on it”. It was a common enough pattern that it warranted first-class inclusion.Improvements to the docs to make this more clear are quite welcome.
Sure. I will start off with smaller questions then.
Lemans description for “dive”:
I understand the basic definition of dive() and I’ve always “dived past HOCs”, usually so I can shallow the Component under test and typically I don’t render children…that’s not a unit test if I do that. I’ve never really use a double shallow either, never have.
What I usually do (which has worked just fine)
So: I personally always just:
a) Isolated/Unit Tests - I do these tests when I TDD: initially shallow render a Component Under test then dive past HOCs. Now the CUT has been shallowed, and I can now test the output of its render()
Example Component that is wrapped in 2 HOCs:
b) Integration Tests: shallow render a Component Under test, then dive past HOCs, and then use a data-test attribute to get focus/pointer to a child, then dive() on that child in order to shallow render that child (maybe for integration tests for example which I rarely do but it’s still possible and I would dive on children rather than double shallow and rather than using mount because it’s still a focused integration test)
Example component with no HOCs and one child:
Questions:
1) What’s to stop me from just using dive for everything - diving past HOCs and/or diving on a child to shallow it? Why would anyone care about double shallow?
2) what “unwrap” means in lower-level technical terms.
.dive()
only works on a wrapper around a single element from a custom component. If you want to shallow on an HTML element, or multiple, you’d need.shallow()
. I think the use cases are rare, but they exist.A typical HOC wraps a
Component
, and injects props, strips props, injects context, and/or hooks into lifecycle methods, and then renders thatComponent
. Shallow wrapping an HOC does not actually exercise any of the code inComponent
, it creates a wrapper around<Component />
itself - you can verify this withwrapper.debug()
. When you.dive()
, you’re throwing away the HOC, and rendering (exercising the code in)Component
, and creating a wrapper around whatComponent
renders. You can dive on any custom component - it’s just that usually you’re doing it on an HOC.It all comes down to keeping components and functions very small and using the right method from enzyme (mount vs shallow). Keeping things small means breaking things apart into more smaller components, smaller functions whatever.
My huge complaint right now are teams who create what I like to call a “Christmas Tree” component. Looks a little something like this, way too many nesting of HOCs with render props OR you have too many children in a container for example of using connect:
That’s real code from a team I was on. This is not simplicity, this cannot be tested. Even if you had 3 nested things, consider breaking it apart, you get beyond one or two nested things whether it’s an HOC or renderProp, you should be breaking it apart.
People should not be using the excuse to use mount because “well that’s what they do or what everyone does” OR “It’s easier to dive past a ton of wrapped things” just to test a very shallow/specific part of a component. Use shallow, it forces you to keep things simple because you’ll feel the pain if you do not. IMO mount is for integration tests. The reason it’s painful to use mount (you find you have to mock a bunch of things in order to test the thing) is because you’re using it for the wrong reasons. Most teams cannot seem to keep components small. So mount ends up being a pain in the ass, lots of setup, lots of mocks to get to a certain part of all that nesting. You’re using the wrong tool in enzyme in that case and your prod design is the actual barrier here. You should use shallow for the most part and use mount for peppering on some integration tests in the end.
If you find yourself using mount() and then find yourself saying “I need to export the component under test”, you’re doing two things wrong. You’re trying to get at something that’s buried (component or file is doing too much) and you’re exporting it because it’s painful to mount. Both of those reasons are because your component or file is not simple. Break stuff apart.
If you’re one like Dodd’s or others who say “shallow sucks because as soon as I change something in my design, shallow tests fail” that’s not shallow’s fault, that’s part of your design sucking and it’s actually giving you feedback on that! It’s saying “Please break me apart so you do not have to dive().dive().dive()”! Sometimes you just have to break stuff apart, and then move tests around, that’s normal not only in React but any language. There is no such thing as “my tests should never break so I should just always use mount to band-aid the pain which is really my design sucking” When you can’t shallow, that’s telling you that your design is too complicated. I do it all the time when I TDD, that’s common to do that. It’s not shallow that’s the devil it’s your design.
Don’t use mount() for everything. Design your stuff as you go with shallow first (TDD can help you with that). Then come back and add integration tests with mount. If people followed this, life would end up being a lot more simpler actually.
So I completely agree with @ljharb. Apply Clean Code, 4 Rules of Simple Design, and keep stuff small and decoupled. That’s always been what allows applications to be maintainable, testable, and extensible.
right it’s not just “that’s not nice”. It makes it hard for anyone trying to work in your code and slows everyone down, plus it’s not easily tested. That’s your design making it painful to test and that’s the kind of feedback we get as we TDD, just we get that very often and our design evolves to be more decoupled as we go because of that design pressure. I don’t care if you TDD or not, just saying that’s what’s going on. That is design feedback telling you to decouple and break apart that component, it’s probably breaking Single Responsibility and the 4 Rules of Simple Design in many ways.
Now we have another problem, Redux doesn’t allow you to pass a store in as a prop in v6. Sigh. I told Mark 2 months ago this would become an issue. Only now is he addressing it as other people are starting to complain.
There’s nothing special about HOCs; it’s just that needing to unwrap an HOC is such a common pattern that
.dive()
is easier than.childAt(0).shallow()
. “unwrap” here just means “shallow-render one level deeper” - since shallow only renders one level deep.Removed my questions doesn’t look like I’m going to get a response…I’ll figure it out and share it in my course.
I think it comes down to test isolation. Ideally the HOC has its own test coverage and consumers don’t have to retest that functionality. If the HOC is from a third party, testing its functionality can help ensure future upgrades don’t break your component. I think each developer has to make the call for themselves as to wether it’s worth the added complexity to test the wrapped component, or if it’s enough to just test the “naked component”. I also think that answer is likely to change from one circumstance to another.
@darkartur because then you’re adding to your production API solely for testing. If the inner component is never used without the wrapper in production, you should not be testing it without the wrapper either.