Or limited depth components, or components with well know boundaries, or isolated components, or scoped components or components you just can just test. And be happy with testing.
The problem with testing React components is quite fundamental. It’s about the difference between unit testing
and integration testing
. It’s about the difference between what we call unit testing and what we call integration testing, the size and the scope. And, while for ages they were no more than different parts of The Testing, now there is a WAR of different testing philosophies.
End
of your’s application — the entry point
,— and to another End
— the deepest deeps. Testing your app as your customer would do. Testing a full spectrum – UI, styles, backend. The only testing kind, which would actually test your application, that’s why it’s so cool.And here are the philosophical problems:
In other words — unit and integration tests have their own benefits and issues, and are not interchangeable, and aimed not to solve different tasks, but to work on different scopes — low component level and high application level. Just because E2E Test approach is an overpower for a component level, while unit testing frameworks are not capable to handle a thing bigger than a module (but you will try).
Symbiosis between units tests and integration — this is what Testing Pyramid should mean. E2E test to ensure cross stack connectivity, Integrations tests, for testing how your application was assembled, and Unit tests, for testing how each and every component work.
But you can not test that “each and every” anywhere, except the unit level due to “combinatorial explosion”.
Let’s imagine you have one component with 2 different states. Only 2 cases to test. Let’s imagine you have 2 components to test — 2x2=4! Let’s imagine 3 components. 3x2 = 6! Just a 6 test cases.
You can test every component by it’s own, independently, and just sum all the test cases to got the required number of tests — linear O(n) growth. For integration tests 3 components already formed 8(2x2x2) combinations, as long as you are testing them simultaneously, and this EXPLOSION — O(n²).
So the rule is simple — use unit tests to test components in isolation, one-by-one, and integration/E2E to test them together. Not to test — ensure that combination is “right”, and wired properly— you may tests happy path only, for example. Or even smoke tests.
Having only integration testing is less than ideal though as unit tests help us design more robust software by easily testing alternate code and failure paths. We should save integration tests for larger “does it really work” kinds of tests. SendGrid
Unit tests are great, as long as in the end you can assemble your application from well unit tested pieces, and then verify the result by a integration test.
This is something I like about node modules – well unit tested pieces, I could rely on.
And the point of everything above is super simple:
But there is a problem...
The majority thinks that Enzyme
, to be more concrete shallow
, is the unit way to unit test. That’s partially true — shallow
gives you ability to test only your component, as long as it does not perform a “full” render.
shallow
is a way tounit test
only your piece of code. But there are so many disadvantages of shallow testing, like RenderProps or Context incompatibility.
Mount does not have these disadvantages, but has others, like testing everything below mount point. Usually it’s a blocker, as long as it common today that something below you, will require something above you — Redux Store, I18nProvider, Context, Apollo, Styled Theme… just everything. And not just to exists, but to have right values.
For a last year I was adding one more wrapper for my test every month.
I would just ask — if you have a simple Button component, is there any difference between shallow and mount?
const Button = ({children, onClick}) => (<button onClick={onClick} className='my-button'>{children}</button>)
😉 — for small components there is no difference between shallow, which just record your React.createElement (result of JSX transpilation) calls, and mount, executed these calls, ie performs a full render. And even there is no difference from browser based tests like webdriver or Cypress. For small components they all the same. For small component the same code could work everywhere. Why?
Why??!!
So — the game changer here is WHAT we call a UNIT we could easily unit test, and “why”.
I mean — you can unit test some component, cos it’s unit testable. Very obviously. Why they are unit testable? Cos the are small? Probably no, this time it’s not so obvious.
Probably, if we define what does “unit” means — we would be able to understand how to archive a better unit tests by making our components more unit, more finite. You probably already understand the idea from the title of this article, but..
React Presentation Components, or Dumb Components — they are quite heavy described in the past, and separation between Smart and Dumb Components was(and is) a Big Thing, especially in Redux applications.
According to Dan Abramov Article Presentation Components are:
And Containers
are just a data/props providers for these components.
In the ideal Application…
Containers are the Tree. Components are Tree Leafs.
The secret sauce here, a one change we have to amend in this definition is hidden inside “May contain both presentational and container components**”, let me cite original article
** In an earlier version of this article I claimed that presentational components should only contain other presentational components. I no longer think this is the case. Whether a component is a presentational component or a container is its implementation detail. You should be able to replace a presentational component with a container without modifying any of the call sites. Therefore, both presentational and container components can contain other presentational or container components just fine.
Ok, but what about the rule, which makes presentation components unit testable – “Have no dependencies on the rest of the app”?
Unfortunately, by including containers
inside presentation
components you are making second infinite, and injecting dependecy to the rest of the app.
You may know how to mock, setup and feed your container
, and how to render your presentation
component, but if it will contain another container, or(usually) containers — you will be unable to unit test your one. It will be already integration tests, and it would require much more work to setup proper environment for ALL containers you actually used and assert the result.
So — it’s simple — PRESENTATION COMPONENTS SHOULD ONLY CONTAIN OTHER PRESENTATION COMPONENTS.
And then — you will be able to unit test your container or your dumb component as a separated, isolated, scoped, and finite thing — A UNIT.
By removing containers from presentation layer you might make your tests easier. But, probably right now you are looking on your code, with lots of containers nested inside Dumb components, and the question you have — “HOW”
How??!!
This is my favourite one. DI, Slots and Rock-n-Roll.
If you need to contain another Container inside Presentation Component — pass it as children
or another slot prop
. As result you will be able to test it with these slots empty. In this case it would be finite
.
// test me with mount and empty slotsconst PageChrome = ({children, aside}) => (<section><aside>{aside}</aside>{children}</section>);
// test me with shallow, I am shallow testableconst PageChromeContainer = () => (<PageChrome aside={<ASideContainer />}><Page /></Page>);
DI probably is the most reusable
solution you can imagine — you are free to “wire” your application differently. This is programing technique, which may make better both your code, and testing.
☝️Containers are the Tree. Components are Tree Leafs.
DI sometimes could be a bit overpower. But what if you will able to contain Containers
inside Presentation
in Production, but not in Testing environment?
const Boundary = ({children}) => (process.env.NODE_ENV === 'test' ? null : children);
const PageChrome = () => (<section><aside><Boundary><ASideContainer /></Boundary></aside><Boundary><Page /></Boundary></section>);
const PageChromeContainer = () => (<PageChrome /> // lets assume that we still need this container :P);
Here it would work “as expected” in dev or prod, but in test env it will not render nested Containers, making PageChrome finite
. Just add more granule control over Boundary — and that’s the deal.
☝️Presentation Component will not contain Containers. But only in test environment.
Almost the same, but with a taste of Layered Architecture — just for every container define Tier, and if Tier is not matching the “current” one — do not render it.
const checkTier = tier => tier === currentTier;
const withTier = tier => WrapperComponent => (props) => ((process.env.NODE_ENV !== ‘test’ || checkTier(tier))&& <WrapperComponent{...props} />);
const PageChrome = () => (<section><aside><ASideContainer /></aside><Page /></section>);
const ASideContainer = withTier(2)(...)const Page = withTier(3)(...)
const PageChromeContainer = withTier(1)(PageChrome);
Tier
here could be almost anything — module, feature, actually “Tier”. In this case Presentation Component could look as “usual”, and Containers themselves would decide should they exists during the tests, or not.
Then you are testing a Component — it’s probably a part of a feature, and could contain another containers from the same feature. You may keep containers you are aware of, and remove others.
Let me cite Dan’s Article yet again:
Remember, components don’t have to emit DOM. They only need to provide composition boundaries between UI concerns.
Feel free to add these boundaries.
The base of concern separation is actually — separation, and your ability to distinguish one thing, from another. Usually it could be done according to the components name.
Let’s assume, that all our Containers has a pattern in their name, and that’s true for the ones, connected to Redux — they all are Connect(ComponentName)
.
Redux is quite good example of the
finite idea
— “connect” is the beginning of everything and the end. It’s aBoundary
by design.
const PageChrome = () => (<section><aside><ASideContainer /></aside><Page /></section>);
const PageChromeContainer = connect()(PageChrome);
// remove all components matching react-redux patternreactRemock.mock(/Connect\(\w\)/)
Using this approach you will be able to test PageChrome
, but not PageChromeContainer
as long as it would be also removed. Let’s create a better example:
import {createElement, remock} from 'react-remock';
// initially allowedconst ContainerCondition = React.createContext(true);
reactRemock.mock(/Connect\(\w\)/, (type, props, children) => (<ContainerCondition.Consumer>{ opened => (opened? (<ContainerCondition.Provider value={false}>// "close" and render real element{createElement(type, props, ...children)}<ContainerCondition.Provider>): null)}</ContainerCondition.Consumer>)
It’s a bit more complex — replaces every connect
with React.Context based condition, which would render only first encountered container, any nested one would be rendered as null
, thus — your Presentation Components will become “finite” in the testing environment, without any actual code change.
PS: reactRemock here is https://github.com/theKashey/react-remock
By the end of a day — you will establish a well known boundaries across different entities, layers, features, modules, or components separated by any other principle. You will be able to use any testing tool — shallow, mount or even webdriver based tools — the tool will not matter anymore.
There are different ways to achive it — more declarative and explicit, like DI, or invisible like remocking. It will not only give you a better testing, but you will be able to create a more scoped Storybooks.
Test the thing you build, focus on details, pick gear by gear. And the only thing you need for it — ability to “pick” a single gear and test it. A single gear, not all the gearbox.
Just Boundaries and Separation.
PS: And yet again — you may test a single gear, and a whole gearbox — but you cant test a half of gearbox, starting from some random stuff in the middle.
Presentational and Container Components_You’ll find your components much easier to reuse and reason about if you divide them into two categories._medium.com
Why I Always Use Shallow Rendering_Tests should help me be confident that my application is working and there are better ways to do that than shallow…_hackernoon.com
SSR: Dependency mocking is the answer!_Or breaking free from side effects and singletons in nodejs and webpack. Long story short – let me explain some things…_hackernoon.com
PS: This article was written as a response to this comment by Dave Schinkel on React RFC. Good testing is not bound to React API Dave!
contextType: convenience API for reading context in a class by gaearon · Pull Request #65 ·…_View formatted RFC Note: I just wrote up the RFC. The semantics were designed by @sebmarkbage._github.com