我喜欢React生态系统的原因在于,IDEA支持许多决策。 许多作者为支持现有秩序写了各种文章,并解释了为什么一切都是“正确的”,所以每个人都知道该党的前进方向正确。
一段时间后,IDEA会发生一些变化,并且一切从头开始。
这个故事的开始是将组件分为容器和非容器(通常称为Dumb Components,对不起我的法语)。

问题
问题很简单-单元测试。 近来,已经有一些集成测试的趋势-众所周知, “编写测试。不是很多。主要是集成”。 。 这不是一个坏主意,如果时间很短(并且不需要特别测试),这就是您需要做的。 我们称其为烟雾测试-验证似乎没有爆炸。
如果有很多时间并且需要测试,则最好不要采用这种方式,因为编写好的集成测试非常非常长。 只是因为它们会越来越大,并且要测试右侧的第三个按钮,您首先需要单击菜单中的3个按钮,并且不要忘记登录。 总的来说-这是银盘上的组合爆炸 。
这里的解决方案是一个简单的(按定义)-单元测试。 以应用程序某些部分的现成状态开始测试的能力。 更准确地说,是将测试区域从应用程序或大块减小(缩小)到较小的单元,无论它是什么。 不必使用酶-如果灵魂要求,您可以运行浏览器测试。 这里最重要的是能够孤立地测试某些东西。 而且没有太多麻烦。
隔离是单元测试中的关键点之一,这就是为什么单元测试不喜欢它的原因。 他们出于各种原因不喜欢它:
- 例如,您的“单元”已从应用程序中撕下,即使其自身的测试是绿色的,它也无法正常工作。
- 或例如因为隔离是在真空中没有人见过的球形马。 如何实现,以及如何衡量?
我个人认为这里没有问题。 当然,在第一段中,您可以推荐集成测试,它是为此而发明的-检查预测试的组件如何正确组装。 您信任npm软件包,这些软件包当然只能对其自身进行测试,而不能对它们本身作为应用程序的一部分进行测试。 您的“组件”与“不是您的”软件包有何不同?
在第二段中,一切都变得有些复杂。 本文正是关于这一点(以及之前的所有内容,即简介),以及如何使“单元” 单元可测试 。
分而治之
将React组件分为“容器”和“演示文稿”的想法并不是新鲜的,描述得很好的,并且已经变得有些过时了。 如果我们以Dan Abramov的文章作为基础(99%的开发人员这样做),那么Presentation Component:
- 关注事物的外观
- 可能同时包含表示性组件和容器组件
**
,并且通常具有一些DOM标记和自己的样式) - 支持插槽(通常允许通过this.props.children进行封闭)
- 与应用程序无关(不依赖于应用程序的其余部分,例如Flux操作或存储)
- 不要依赖数据(不要指定如何加载或更改数据)
- 该接口基于道具(仅通过道具接收数据和回调)
- 通常是无状态的(很少有自己的状态(当有状态时,它是UI状态而不是数据状态))
- 通常是SFC(除非需要状态,生命周期挂钩或性能优化,否则都写为功能组件)
好的,原则上,容器是所有逻辑,所有对数据的访问以及整个应用程序。
在理想的世界中,容器是树干,展示组件是叶子。
Dan的定义有两个关键点: “独立于应用程序” ,这几乎是学术上对“单元”的定义,以及*“可能同时包含其他表示组件和容器**
” *,这些星星特别有趣。
(免费翻译)**在我文章的早期版本中,我(丹)说,表示组件应仅包含其他表示组件。 我不这么认为了。 组件的类型是详细信息,并且可能会随时间变化。 通常,不要共享它,一切都会好的。
让我们记住之后发生的情况:
- 在故事书中,所有内容都下降了,因为某种容器在左侧的第三个按钮中爬入了没有该容器的一侧。 对graphql,react-router和其他react-intl的特殊问候。
- 失去了在测试中使用mount的能力,因为它渲染了从A到Z的所有内容,并且又在渲染树的某个深处某处某人执行了某项操作,并且测试失败了。
- 失去了控制应用程序状态的能力,因为失去了使选择器/分解器(尤其是proxyquire)变湿的能力,并且整个门需要变湿。 这对于单元测试很酷。
如果您认为问题有点牵强,请尝试将这些容器(将用于非容器中,在其他部门中使用)进行小组合作,结果,您和他们查看了测试,而您不明白为什么昨天所有它起作用了,现在又起作用了。
结果,您必须使用浅表层, 通过设计消除了所有有害(和意想不到的)副作用。 这是“为什么我总是使用浅表”一文中的一个简单示例
想象一下,工具提示呈现“?”,单击时将显示类型本身。
import Tooltip from 'react-cool-tooltip'; const MyComponent = () => { <Tooltip> hint: {veryImportantTextYouHaveToTest} </Tooltip> }
怎么测试呢? 挂载+单击+检查可见的内容。 这是一个集成测试,而不是一个单元,问题是如何为您单击“外部”组件。 浅层没有问题,因为没有大脑 ,也没有“外来成分”本身。 但是这里有头脑,因为Tooltip是容器,而MyComponent 实际上是表示形式。
jest.mock('react-cool-tooltip', {default: ({children}) => childlren});
但是,如果您对cool-tooltip做出反应,那么测试就不会有问题。 “组成部分”急剧变笨,变短,变得更加有限 。
最终组成
- 具有众所周知大小的组件,可以包括其他先前已知的最终组件,或者根本不包含它们。
- 不包含其他容器,因为它们包含不受控制的状态和“增加”的大小,即 使当前分量无限 。
- 否则,它是常规的演示组件。 实际上,与Dan的文章的第一个版本完全相同。
最后的组件只是从大型机构取出的齿轮。
整个问题是如何将其取出。
解决方案1-DI
我最喜欢的是依赖注入。 丹也爱他 。 通常,这不是DI,而是“插槽”。 简而言之-无需在Presentation内部使用容器-需要将它们注入那里。 在测试中,有可能注入其他东西。
当“容器是主干,表示组件是叶子”时,情况就是这样。
解决方案2-边界
DI通常可以很酷。 大概现在%username%认为应该如何在当前代码库中应用它,而该解决方案还没有发明出来……
在这种情况下, Borders会救您。
const Boundary = ({children}) => ( process.env.NODE_ENV === 'test' ? null : children
在这里,所有的“转换点”都代替了“槽”,而只是变成了“边界”,它将在测试期间渲染任何东西 。 足够声明 ,并且确切地说是您需要“卸下齿轮”。
解决方案3-层
边框可能会有些粗糙,通过添加一些有关Layer的知识,使边框变得更聪明可能会更容易。
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('UI')(...) const Page = withTier('Page')(...) const PageChromeContainer = withTier('UI')(PageChrome);
在“层/层”的名称下,可以有不同的内容-功能,存储区,模块或只是该层/层。 关键并不重要,主要的是您可以拉动齿轮,也许不是一个齿轮,而是最终数字, 以某种方式在所需和不需要之间划清界限(对于不同的测试,这是不同的边界)。
没有什么能以不同的方式标记这些边界。
解决方案4-单独的问题
如果解决方案(按照定义)在于实体的分离-如果我们采用并分离它们,将会发生什么?
我们非常讨厌的“容器”通常称为容器 。 如果不是这样,那么什么也无法阻止现在开始以某种方式更响亮地命名组件。 或者它们的名称中具有某种模式-Connect(WrappedComonent)或GraphQL / Query。
如果在运行时正确地基于名称在实体之间划界怎么办?
const PageChrome = () => ( <section> <aside><ASideContainer /></aside> <Page /> </section> );
在测试中加上一行,然后react-remock将删除可能干扰测试的所有容器。
原则上,该方法可用于测试容器本身-您只需要删除除第一个容器以外的所有内容。
import {createElement, remock} from 'react-remock';
再次-删除了几条线和齿轮。
合计
在过去的一年中,测试React组件变得更加复杂,尤其是对于挂载而言-您需要覆盖所有10个提供者,上下文,并且越来越难以以正确的样式测试正确的组件-要拉的绳子太多了。
有人吐口水,进入了浅薄的世界。 有人在单元测试中挥舞着手,然后将所有东西转移给了赛普拉斯(像走路一样走路!)。
有人轻弹了一下反应,说这是代数效应 ,您可以做任何您想做的事情。 以上所有示例实质上都是对这些代数效果和模拟的使用。 对我和DI而言,这些都是魔基。
PS:这篇文章是对React / RFC中关于React团队破坏了一切以及那里所有聚合物的事实的回应。
PPS:这篇文章实际上是另一篇文章的非常免费的翻译
PPPS:通常,要真正隔离,请查看rewiremock