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.
For reference, I tweaked the code for .dive and .shallow to be more similar, which makes the differences more obvious.
shallow(options) {
const adapter = getAdapter(this[OPTIONS]);
return this.single('shallow', n => {
const el = adapter.nodeToElement(n);
return this.wrap(el, null, options)
});
}
dive(options = {}) {
const adapter = getAdapter(this[OPTIONS]);
const name = 'dive';
return this.single(name, (n) => {
if (n && n.nodeType === 'host') {
throw new TypeError(`ShallowWrapper::${name}() can not be called on Host Components`);
}
const el = adapter.nodeToElement(n);
if (!isCustomComponentElement(el, adapter)) {
throw new TypeError(`ShallowWrapper::${name}() can only be called on components`);
}
return this.wrap(el, null, { ...this[OPTIONS], ...options });
});
}
.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.
In particular, the mantra i often use is "when shallow rendering, one .dive() per HOC"
this example show everything clearly:
it('calls actions as expected when toggling switches', () => {
const wrapper = shallow(
<CameraSettings />,
{ context: { store: mockStore(initialState) } },
);
const render = wrapper.dive();
render.find('Switch').forEach(child => {
child.simulate('valueChange');
});
});
this example show everything clearly:
That鈥檚 a decent example of how to use dive, but shallow would work there just the same.
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 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
It's alright I need to move ahead anyway with stuff..I need to come to a conclusion one way or another and felt my post was pretty lengthy anyway. I could put it back up, if you feel like you want to answer it later when you have time
maybe a shorter question? :-)
Sure. I will start off with smaller questions then.
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.
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:
const company = shallow(<Company />).dive().dive() // diving past two HOCs and shallow rendering the Component under test
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:
<Company>
<Logo data-test="company-logo">
</Company>
const company = shallow(<Company />);
const logo = company.find("find the attribute company-logo here").dive() // now I've shallowed the child
.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 that Component. Shallow wrapping an HOC does not actually exercise any of the code in Component, it creates a wrapper around <Component /> itself - you can verify this with wrapper.debug(). When you .dive(), you're throwing away the HOC, and rendering (exercising the code in) Component, and creating a wrapper around what Component _renders_. You can dive on any custom component - it's just that usually you're doing it on an HOC.
thanks @ljharb yea I'm not really getting deep enough into the underlying enzyme implementation in our discussion. Particularly how it's working under the hood in technical details terms. I am going to just take a look at the enzyme implementation (source) to understand it a little more than just the high level which I already understand. I'm not satisfied with not understanding with stoping at lemans terms saying "throwing away the HOC with dive" or "unwrap an HOC". I get that it lets you past it (ignore / get past the the HOCs so you can get to the rendered output of your component under test), and it's what I've done for a long time now my myself when I test for over 3 years now in fact. And it works, but I just want to know more. I think shallow does what, it only does a ReactDOM.render or something like that, maybe it's createElement under the hood with enzyme? I want to know stuff at that detail at that level.
But it sounds like for 95% of devs out there, just use dive(). Shallow then dive() on HOCs and dive() on child components is really what it boils down to. I personally don't have a use case for double shallowing, and it seems bizarre one would want to shallow on an HTML element (instead of on a React component).
I'll take a look at the enzyme source more. Thanks for your help though.
shallow doesn't use ReactDOM at all; only mount does.
.dive() literally is nothing more than sugar for 1) throw if it's not a single custom component, 2) call .childAt(0).shallow() on it.
yea what I'm trying to get at is what's happening after shallowing the Component that's wrapped in an HOC, how the HOC is dealt with under the hood with enzyme in its source code...so that I understand what "unwrap" truly means.
it's using renderElement under the hood I see, yea, not ReactDOM so ignore that.
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.
Thanks @ljharb !
Why do we need .dive() for HOCs when we can just do this:
// MyComponent.js
export default compose(
withRouter,
pure,
withStyles
)(MyComponent)
export { MyComponent as MyComponentNaked }
// MyComponent.spec.js
import {MyComponentNaked as MyComponent} from './MyComponent'
Now we don't care about compose magic and have more unit tests
@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.
@ljharb I understand your point of view but I think my approach is still useful and sometimes more effective
It鈥檚 useful in that you鈥檙e still able to exercise the inner code in tests; dive allows the same. I鈥檓 skeptical it鈥檚 more effective though, unless there鈥檚 parts of your architecture that might need to be re-evaluated.
I've also seen developers export "naked components" for easier testing. Some "controller" components have lots of HoCs that just fetch some simple values from the context, like language, user ID, certain app states, screen size, apollo GraphQL data, routing params... you name it. When you test the inner "naked" component only, you can just supply simple values into the props. When you try to test the outer component with all the HoCs, there's lots of mostly pointless context mocking involved. Ofc it is not very nice to have loads of HoCs attached, but sometimes code just evolved that way and all other solutions get even more messy.
My intuition is that if testing a component the way it's used in production is messy, then you may wish to reevaluate your component architecture.
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.
https://til.hashrocket.com/posts/drkftm56yy-spelunking-through-components-with-enzymes-dive is pretty straightforward :+1:
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.
it is not very nice to have loads of HoCs attached
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.
I haven't tried it because I always dive() past connect wrappers, but pretty sure if you just test the exported bare component that is being wrapped by connect, if you do like I do which is to test mapStateToProps indirectly you can't. You need to dive past the connect container in order to ensure that connect is still wired up so that when you want to test mapStateToProps for example indirectly you can.
I can't be certain (I need to try it) but pretty sure if you just test the exported component, you're losing the the redux state mechanism/lifecycle that would allow you to indirectly assert that your internals are wired up. And no, I'm not saying "Test Redux", I'm saying I'm testing that mapStateToProps is even formed well, that whoever maintains or creates that function or changes it, has implemented it right...the mapToProps function's implementation. At anytime someone could change something and it breaks, and I want my tests to give me that feedback instantly if that happens...but I don't want to export internals like mapStateToProps to do so.
Most helpful comment
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