Latest news about Bitcoin and all cryptocurrencies. Your daily crypto news habit.
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
Let me highlight few moments âwhy to use shallowâ, Kent collected, and then myth-busted! Ie false reasons to shallow rendering.
- ⊠for calling methods in React components
- ⊠it seems like a waste to render all of the children of each component under test, for every test, hundreds/thousands of timesâŠ
- For actual unit testing. Testing composed components introduces new dependencies that might trigger an error while the unit itself might still work as intended.
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.
Why I Never Use Mount Rendering
- The first reasonâââitâs slow. Not actually âjustâ slow, but âunpredictableâ slow. 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.
- The secondâââit could work today. But tomorrow, then you will made some changes to the underlying componentâââit will broke. And you cannot predict who, why and when will made that change. And who will have to fix it.
- The thirdâââit does not work. ReallyâââI could not get the point why anybody âshould prefer 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.
body[data-twttr-rendered="true"] {background-color: transparent;}.twitter-tweet {margin: auto !important;}
When I'm working on an interesting programming task, my number one trick is to delete the whole thing and start over. Eventually I arrive at a version I'm reluctant to throw out. That's when I know it's good enough to start iterating.
âââ@acdlite
function notifyResize(height) {height = height ? height : document.documentElement.offsetHeight; var resized = false; if (window.donkey && donkey.resize) {donkey.resize(height); resized = true;}if (parent && parent._resizeIframe) {var obj = {iframe: window.frameElement, height: height}; parent._resizeIframe(obj); resized = true;}if (window.location && window.location.hash === "#amp=1" && window.parent && window.parent.postMessage) {window.parent.postMessage({sentinel: "amp", type: "embed-size", height: height}, "*");}if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.resize) {window.webkit.messageHandlers.resize.postMessage(height); resized = true;}return resized;}twttr.events.bind('rendered', function (event) {notifyResize();}); twttr.events.bind('resize', function (event) {notifyResize();});if (parent && parent._resizeIframe) {var maxWidth = parseInt(window.frameElement.getAttribute("width")); if ( 500 < maxWidth) {window.frameElement.setAttribute("width", "500");}}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â.But you might not have side effects, I am talking about.
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.
But I could just access props.children and test MYÂ stuff!
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.
Why I Always Use Shallow Rendering
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
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.
And next I will explain the main point of the whole article
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âŠ
- Unit tests stands for putting small functions and small components into specific situations, and testing that everything works as predicted.
- E2E tests stands for testing actual browser work.
- PDVâââthe same as E2E but only with real environment.
- Integration tests (and E2E) are testing how application is WIRED.
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?
And the question you have to answer is following:
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:
- Unit tests small pieces. Atoms.
- Shallow tests small pieces combination. Molecules.
- Cypress tests result as a whole. Organism.
When shallow sucks.
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.Lets try to solve the problem with RenderProps
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.Anywayâââhow to solve renderProp issue with shallow
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.
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);
body[data-twttr-rendered="true"] {background-color: transparent;}.twitter-tweet {margin: auto !important;}
Yesterday went into a testing issue caused by renderProps, and stateful component. 1. You have to test payload you provided. 2. You can't `shallow` renderProps. It will return a function 3. You can't `mount` stateful component, as long it will not render payload. But what if...
âââ@theKashey
function notifyResize(height) {height = height ? height : document.documentElement.offsetHeight; var resized = false; if (window.donkey && donkey.resize) {donkey.resize(height); resized = true;}if (parent && parent._resizeIframe) {var obj = {iframe: window.frameElement, height: height}; parent._resizeIframe(obj); resized = true;}if (window.location && window.location.hash === "#amp=1" && window.parent && window.parent.postMessage) {window.parent.postMessage({sentinel: "amp", type: "embed-size", height: height}, "*");}if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.resize) {window.webkit.messageHandlers.resize.postMessage(height); resized = true;}return resized;}twttr.events.bind('rendered', function (event) {notifyResize();}); twttr.events.bind('resize', function (event) {notifyResize();});if (parent && parent._resizeIframe) {var maxWidth = parseInt(window.frameElement.getAttribute("width")); if ( 500 < maxWidth) {window.frameElement.setAttribute("width", "500");}}
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.
Well, when to use mount?
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.
An example?
const Application = () => ( <ThemeProviders> <TopMenu /> <Routes /> <-- contains side effect. could not test <BottomMenu /> </ThemeProviders>)
///////////////////////////////////////////////
// now - testable by mountconst ApplicationChrome = ({children}) => ( <ThemeProviders> <TopMenu /> {ApplicationChrome} <--- not contain anything <BottomMenu /> </ThemeProviders>)
// now - testable by shallowconst Application = () => ( <ApplicationChrome> <--- testable by mount <Routes> </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.
body[data-twttr-rendered="true"] {background-color: transparent;}.twitter-tweet {margin: auto !important;}
@dceddia I think this is one of the biggest misunderstandings about React. It would be great to highlight this pattern more. Note how by making <Nav> and <Body> accept any elements as children, I removed the need to pass the "user" prop down through them.
âââ@dan_abramov
function notifyResize(height) {height = height ? height : document.documentElement.offsetHeight; var resized = false; if (window.donkey && donkey.resize) {donkey.resize(height); resized = true;}if (parent && parent._resizeIframe) {var obj = {iframe: window.frameElement, height: height}; parent._resizeIframe(obj); resized = true;}if (window.location && window.location.hash === "#amp=1" && window.parent && window.parent.postMessage) {window.parent.postMessage({sentinel: "amp", type: "embed-size", height: height}, "*");}if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.resize) {window.webkit.messageHandlers.resize.postMessage(height); resized = true;}return resized;}twttr.events.bind('rendered', function (event) {notifyResize();}); twttr.events.bind('resize', function (event) {notifyResize();});if (parent && parent._resizeIframe) {var maxWidth = parseInt(window.frameElement.getAttribute("width")); if ( 500 < maxWidth) {window.frameElement.setAttribute("width", "500");}}
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.
body[data-twttr-rendered="true"] {background-color: transparent;}.twitter-tweet {margin: auto !important;}
In my Ruby code, half of my methods are just one or two lines long. 93% are under 10. https://t.co/Qs8BoapjoP https://t.co/ymNj7al57j
âââ@martinfowler
function notifyResize(height) {height = height ? height : document.documentElement.offsetHeight; var resized = false; if (window.donkey && donkey.resize) {donkey.resize(height); resized = true;}if (parent && parent._resizeIframe) {var obj = {iframe: window.frameElement, height: height}; parent._resizeIframe(obj); resized = true;}if (window.location && window.location.hash === "#amp=1" && window.parent && window.parent.postMessage) {window.parent.postMessage({sentinel: "amp", type: "embed-size", height: height}, "*");}if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.resize) {window.webkit.messageHandlers.resize.postMessage(height); resized = true;}return resized;}twttr.events.bind('rendered', function (event) {notifyResize();}); twttr.events.bind('resize', function (event) {notifyResize();});if (parent && parent._resizeIframe) {var maxWidth = parseInt(window.frameElement.getAttribute("width")); if ( 500 < maxWidth) {window.frameElement.setAttribute("width", "500");}}!! 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.
Side note about splitting components
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.
Conclusion
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.
Why I Always Use Shallow Rendering was originally published in Hacker Noon on Medium, where people are continuing the conversation by highlighting and responding to this story.
Disclaimer
The views and opinions expressed in this article are solely those of the authors and do not reflect the views of Bitcoin Insider. Every investment and trading move involves risk - this is especially true for cryptocurrencies given their volatility. We strongly advise our readers to conduct their own research when making a decision.