Components — the same and the different
Tests should help me be confident that my application is working and there are better ways to do that than shallow rendering.
This was written not by me, but by Kent C. Dodds, in his resent article, which was named more or less, but absolutely opposite to this one.
Why I Never Use Shallow Rendering_Tests should help me be confident that my application is working and there are better ways to do that than shallow…_blog.kentcdodds.com
Let me highlight few moments ”why to use shallow”, Kent collected, and then myth-busted! Ie false reasons to shallow rendering.
He “myth-busted” all of the points, proving to all of us, that we shall never use shallow rendering for testing our components. It’s not reliable, not testing anything, and just doesn’t worth time spent on it.
Let me do the same(myth-bust), but for mount. Let me strike back.
mount
renders just everything. There is no limits. When you want to test your small component – you will test a bit more than you may expect, and spent much more time than you expect. But this is not the issue, 1000 tests still could be executed in 10 seconds.mount
over shallow”
, while **mount**
does not work.Mount does NOT WORK. As long it does TOO MUCH WORK.
Really — mount
renders everything from “here” and to the very end. Including all the nested Components and calling all the lifecycle methods of those components, making lots of side effects, you placed inside lifecycle methods, just because of “Component Approach”, of course. Side effects, you may not be aware of, or you just don’t want to execute. Or can’t. Or should’t execute in a testing environment.
Some side effects could NOT be mitigated even with mocking. This moment is usually explained in articles about mocking, in short — don’t mock not dirrect dependencies. In another words — as long you are not controlling nested Components, you gonna render in mount, you are not able mock out the “bad Components”. They are not bad — they are nested. And everybody could just deeply refactor them.
For example deeply nested component is fetching some information, loading third party script and so on. For example – Facebook Like button. You could not mock it – that not dependency of a component you are testing. Don’t mock, and honestly don’t test, stuff you are not in charge of.
If your component is a bit bigger than nothing, has some Redux, Ajax, any other stuff anywhere inside — then probably you could not use mount. Just could not.
Shallow rendering is the only way to run only the code, you have intentions to run. Only “subject under test”.
Side effects are not the main issue for mount
. The main issue lays in another dimension — mount renders component, as it will be rendered in real. In all the details possible.
For some of you this is the reason why you should use mount, but for me – opposite. I am afraid of the real realty. Too real for me.
Let’s imagine you have Tooltip element, and you are passing content inside using standard “children
interface”.
import Tooltip from 'react-cool-tooltip';
const MyComponent = () => {<Tooltip><div>something: {veryImportantYouHaveToTest}</div></Tooltip>}
Now test it. It is not as simple as it look like. Lets face the truth — it is, yet again — impossible. So small and simple component, but you can’t test it. At least I can’t. You don’t believe? Let me elaborate more:
Tooltip will not render anything, unless someone clicks on it, only then it will display children. “Clicking on it” is a part of a public interface, a “contract”, but do you see anything here, you can click on? That’s internal realization of Tooltip. There is no information how to “use” it.
You probably could just open Tooltip sources, and find the way to match a button inside. Probably it’s just button inside. Or should you pick it by class name, or A11n role? Or match by text as Kent C. Dodds proposes in his react-testing-library? But are you controlling that text? What if in the future library author will decide to make “that button” more fancy?
You can’t get the answer to these question, cos they are a subject to change. Tooltip could change internal realisation, as long as it is a third party library you are not controlling. And it will break all the tests. Accidentally.
This is something “you shall no do”, and requirement of isolation is explained in every second test-related book. And I am not sure why “react” unit testing should differ from “js”, “java”, and ”just code” best testing principles. Look like they should not. And the best precipices are quite simple: test only your stuff. Simple? And you have agreed to it, as long you are not testing how CPU, HardDrive, or Browser works. That is not your stuff, and you don’t care.
Yeah! No more talks, lets just solve the problem!
expect(wrapper.find(Tooltip).props().children).toBe(something)
But it will give you “raw” elements, you can’t assert. You have to mount
intermediate render results, wrap with some usable helpers, and then assert:
expect(mount(wrapper.find(Tooltip).props().children)).toBe(value)
So – now you could assert, that you passed right props down to Tooltip, but it sounds like – you don’t have to mount
“parent” element, only chidren
.
You also could mock Tooltip, and make it always just render children. Replace smart Tooltip but dumb component. But it sounds like – you don’t need mount
— shallow
will do it better.
There is only one reason – I am able to test the stuff I could test. And that stuff is limited to my current local “scope”. Usually – the single component.
Shallow just records calls to React.createElement but never _creates_ any element. That’s why it does not render ANYTHING else.
If I want to render nested component in real – I should explicitly say it – wrapper.find(Tooltip).dive()
. Without dive – Tooltip is a “tag”, not a component, but just a record in memory. Deep diving could fight for the good, and could be the bad. Kumar McMillan know more about it, and how deep diving could save the day:
Testing Strategies for React and Redux - Mozilla Hacks - the Web developer blog_When the Firefox Add-ons team ported addons.mozilla.org to a single page app backed by an API, they chose React and…_hacks.mozilla.org
This article clearly, may be much more clearly than this one, explains why you often have to use shallow, and how to escape from shallow design limitations.
What does shallow or mount tests actually tests? May be they test components? Do you have another tests, which tests not components, but “code”, or “application”? I could name just a few:
Smoke tests, unit tests, integration tests, end-2-end tests, PDV tests…
Every type of tests are testing somehow better than another, testing the stuff from different prospective, giving different confidence, different not in quantity or quality, but in different colour of confidence.
Mount is good for unit and integration testing, could check the “wiring” of the App. But Cypress could do it even better. What about Storybook storyshots?
What shallow testing could provide for you? What it does better? That shallow can do, mount cannot? May be not better, but from another angle?
Shallow Tests are Structural Tests.
Shallow not letting you test how your component will work, but let you to test how it was assembled.
If you think you have assembled component composition correctly, and tested it – then you have tested all the stuff you should test. If then code doesn’t work in “real” – then or your assertion was not quite correct, or your peers, other pieces of code, not the current-component-under-test, are broken. That’s their fault, not yours. It’s a goal of unit testing — spot a real place of a problem, not just spot the problem.
Shallow is a way to test component composition.
In my expirence shallow tests are everything you need. Mount will not give as much confidence as Cypress-i-will-test-a whole-app could do, and even could require MUCH more effort. As I said — tests a different.
As result — the maximal confidence is a result of a test composition, or testing pyramid:
Yeah — I just said that shallow sucks. First because of the way it works – for example it’s absolutely incompatible with renderProps. Second – enzyme, by its own, was not compatible with React 16 stuff, including Context API, now used everywhere.
Now enzyme is fixed, and look like will never “broke” again.
As I mention above — shallow could see only React.createElement
commands, called in the current component. With renderProps code is hidden inside function-as-children is not executed untill “parent componet” will “render” it. For shallow — it, and everything inside it, does not exists.
You can dive
into a Component made of RenderProps, but that it may require more than one dive, having in mind quite heavy usage of Context API nowadays.
Second problem — not every dive into component will provide a result you are able to use. And long ContextAPI.Consumer, for example, could not have corresponding Context.Provider.
Third problem — then you dive — you are leaving current location, and never knew where you will emerge again. In depends on realization of a component, you are diving into. And that could be not-controlled component.
You are shallowing a virtual ReactTree, but diving into a Real one.
And you are also loosing ability to “overview” all the stuff you are rendering, which actually distinguish shallow from mount.
Mocking here will also NOT help you, as long mocking is mocking a realisation, and shallow doesn’t give a shit about realisation, only about composition. Mocking will help only if you dive into the component, but, just a few lines above I’ve said — diving is not for every case.
That’s a design limitation we have to live with.
Simple! Just Mock component, even if I just said that you could not use mocks, even if I just said that mocking will not work. Feel free to use another version of mocking — not dependency mocking, but method overriding. Not .mock
, but .stub
.
Lets pick almost school example —
and almost school solution — just “unwrap” render prop
You may automate this operation — just mock React.createElement, and createElement
as you want. For example — instantly call all function-as-children, if you know how to call em.
const createElement = jest.spyOn(React, "createElement");
createElement.mockImplementation(({type, props, children) => doWhatEverYouWant);
createElement
get’s component type and children as input, and, actually, returning the same information it was given. That is an instruction to React, and that’s the only information shallow will work with. By controlling React.createElement you are controlling shallow rendering.
I’ve created a special library for this, capable to solve puzzles like this, and the others. I am not trying to solve the problem by brute force, but look at on from another angle. Could not use dependency mocking — don’t use it. Simple.
theKashey/react-remock_react-remock - Get any Component replaced. Anywhere. 🛠♻️_github.com
The second, and almost the same variant is to use enzyme-extension library
commercetools/enzyme-extensions_enzyme-extensions - 🎩 Enzyme extensions to test shallowly rendered enzyme wrappers 🏄🏻_github.com
There is a little difference how they work — here is a comparison:
Well, always, when you can. And the only problem — you don’t always CAN. But that is not a problem of mount
, but the problem of the code you written.
Hard to test code is hard to 😍 code.
And the only way — make the code be more testable. For me it is the law of the universe — if you can’t do something, for example test — split that something into the smaller, “doable”, pieces. Divide and Conquer.
A divide and conquer algorithm works by recursively breaking down a problem into two or more sub-problems of the same or related type, until these become simple enough to be solved directly.
Dissolve one big component into the smaller ones, you can test using mount, then — test their composition using shallow.
const Application = () => (<ThemeProviders><TopMenu /><Routes /> <-- contains side effect. could not test<BottomMenu /></ThemeProviders>)
///////////////////////////////////////////////
// now - testable by mountconst ApplicationChrome = ({children}) => (<ThemeProviders><TopMenu />{children} <--- MAY not contain anything<BottomMenu /></ThemeProviders>)
// now - testable by shallowconst Application = () => (<ApplicationChrome> <--- testable by mount<Routes> <--- "untestable" routes are injected in the container</ApplicationChrome>)
It’s more about DI(D in SOLID), the Dependency Injection pattern, when you can create your Application without internals, without blocks containing side-effects, preventing a whole Component from being tested. And then inject those internals. This is something Dan Abramov is talking about in his props drilling twitts, but from a testing point of view.
Just try to apply best practices from “normal programing” to “react programing”. Split big components into the smaller and testable ones. This is like Container/Component separation in Redux, everybody is aware of, just without Redux. This is something about DRY.
!! I would not say that splitting React components, as thus making more and more components is a good idea from performance point of view. It is not. Be pragmatic, and maintain ballance. This is not a silver bullet you can always use !!
I would not say that splitting is even possible with renderProps, as long it’s super common, when code inside function-as-children uses variables from the parent scope. If you will move function off the component — you will have to change call signature, to “take variables with you”. But problems like this were solved for “function composition”, and not a problems — they are just requiring a bit different code taste.
const MyComponent = (props) => {<Context.Consumer>{ (contextValue) => ( <--- not "easy" testable by shallow<div>{props.value} 😍 {contextValue} <AndSideEffect /></div>)}</Context.Consumer>}
///////////////////////////////////////////////////
// testable as using shallow// interface is still "component-compatible"const RendedProp = (props, contextValue) => (<div>{props.value} 😍 {contextValue} <AndSideEffect /></div>)
// just move MyComponent to another file, and you will able// to "mock" RendedProp using jest.mock
// now testable by mount.const MyComponent = (props) => {<Context.Consumer>{(contextValue) => RendedProp(props, contextValue)}// or{(contextValue) =><RendedProp props={props} contextValue={contextValue} />}</Context.Consumer>}
Hint: In the last example I’ve used renderProp
as RenderProp
, in ComponentCase convention, but called it as a function. You probable saw this trick before, and some one even proposes, that it speed ups whole rendering.
Calling SFC as a functions is something React does underneath. You are just taking it’s job, and “inlining” “components” instead of “creating” them. That is the different? Actually, internals of components, rendered this way, are visible to shallow 😉. Yet another power tool to amend shallow testing, but please don’t overuse it.
Proper splitting could save the day. Extracting all the side-effects you may have inside Components to the route level, using redux-first-router, could make tests easier.
Splitting Components and Containers — makes both components MUCH more testable, than their union.
Sometimes you don’t need a better tool, to better do something. You need another point of view. Another approach. Keep this in your mind.
As the conclusion — I would say — only a Sith deals in absolutes. Mount
is a tool, shallow
is tool, they completes each other. Other tools and testing from another angles and another dimensions — completes each other.
I am using Enzyme, React-test-renderer, Cypress, Nightwatch, Jest, Sinon, Mocha, Snapshots, Screenshots, and maintaining some huge projects without a single test.
I am using different approaches to test my libraries, my components, my applications, and combinations of applications.
Just try to find out that is better for you, and for you team. Don’t just say — never use shallow. Never say never. Divide and Conquer.