
我们有问题。 测试的问题。 测试React组件的问题,这是非常基本的。 这是关于unit testing
和integration testing
之间的区别。 这是关于我们所谓的单元测试和我们所谓的集成测试,大小和范围之间的区别。
这与测试本身无关,而与组件体系结构有关。 关于测试组件 ,独立库和最终应用程序之间的区别。
每个人都知道如何测试简单的组件(它们很简单),可能知道如何测试应用程序(E2E)。 如何测试有限和无限的事物...
定义问题
有2种不同的方法来测试React组件- shallow
以及其他所有方法,包括mount
, react-testing-library
, webdriver
等。 只有shallow
是特殊的-其余的行为相同。
而这种区别在于大小和范围 -关于将要测试的内容,以及部分如何进行测试。
简而言之- shallow
将仅记录对React.createElement的调用,而不会运行任何副作用,包括渲染DOM元素-这是React.createElement的副作用(代数)。
任何其他命令都将运行您提供的附带副作用的代码。 正如现实中那样,这就是目标。
问题出在下面: you can NOT run each and every side effect
。
为什么不呢
功能纯度? 纯度和不变性-今天的圣牛。 而您正在屠杀其中之一。 单元测试的公理-没有副作用,隔离,嘲笑,一切都在控制之下。
也许,如果我们定义“正确组件”的规则,我们可以轻松地进行测试-它会指导我们并为我们提供帮助。
TRDL:有限分量
智能和哑巴组件
根据Dan Abramov的文章介绍,组件包括:
- 关注事物的外观。
- 可能同时包含表示性组件和容器组件
**
,并且通常具有一些自己的DOM标记和样式。 - 通常允许通过this.props.children进行遏制。
- 与其他应用程序无关,例如Flux操作或商店。
- 不要指定如何加载或更改数据。
- 仅通过道具接收数据和回调。
- 很少有自己的状态(当这样做时,它是UI状态而不是数据状态)。
- 除非需要状态,生命周期挂钩或性能优化,否则它们被编写为功能组件。
- 示例:页面,边栏,故事,用户信息,列表。
- ....
- 容器只是这些组件的数据/道具提供者。
根据起源: 在理想的应用中...
容器就是树。 组件是树叶。
在黑暗的房间里找到黑猫
这里的秘密之处是我们必须对此定义进行修改的一项更改,隐藏在“可以包含表示性和容器性组件**
”中 ,让我引用原始文章:
在本文的早期版本中,我声称呈现组件应仅包含其他呈现组件。 我不再认为是这种情况。 组件是表示组件还是容器是其实现细节。 您应该能够用容器替换演示组件,而无需修改任何呼叫站点。 因此,外观组件和容器组件都可以包含其他外观组件或容器组件。
好的,但是规则使演示组件可以测试的规则又如何- “对应用程序的其余部分没有依赖性” ?
不幸的是,通过将容器包括到演示组件中,您使第二个容器成为无限 ,并向应用程序的其余部分注入了依赖性。
可能那不是您打算要做的。 因此,我别无选择,只能使哑组件变得有限:
表示组件应仅包含其他表示组件
唯一的问题是,您应该问(调查当前的代码库): 如何? :tableflip:?!
如今,Presentation Presentation Components和Container不仅纠缠在一起,而且有时还没有提取为“纯”实体(您好GraphQL)。
解决方案1-DI
解决方案1很简单-不要在哑组件中包含嵌套容器-包含slots
。 只需接受“内容”(孩子们)作为道具,就可以解决问题:
- 您无需“应用程序的其余部分”就可以测试哑巴组件
- 您可以使用Smoke / Integration / e2e测试而不是测试来测试集成。
// Test me with mount, with "slots emty". const PageChrome = ({children, aside}) => ( <section> <aside>{aside}</aside> {children} </section> ); // test me with shallow, or real integration test const PageChromeContainer = () => ( <PageChrome aside={<ASideContainer />}> <Page /> </PageChrome> );
丹本人批准:
{%twitter 1021850499618955272%}
DI(依赖注入和依赖倒置)可能是这里最可重复使用的技术,它可以使您的生活变得轻松得多。
指向此处-愚蠢的组件变得愚蠢!
解决方案2-边界
这是一个说明性的解决方案,可以扩展Solution 1
仅声明所有扩展点。 只是用...包裹它们Boundary
const Boundary = ({children}) => ( process.env.NODE_ENV === 'test' ? null : children // or `jest.mock` ); const PageChrome = () => ( <section> <aside><Boundary><ASideContainer /></Boundary></aside> <Boundary><Page /></Boundary> </section> );
然后-您可以将Boundary
禁用为零,以减小Component范围并将其设为finite 。
指向此处-边界在“哑”组件层上。 哑巴组件控制着哑巴的程度。
解决方案3-层
与解决方案2相同,但具有更智能的边界,能够模拟layer或tier或您所说的任何内容:
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
组件connect
到Redux或GQL,您将生产出众所周知的容器。 我的意思是- 众所周知的名称Container(WrapperComponent)
。 您可以嘲笑他们的名字
const PageChrome = () => ( <section> <aside><ASideContainer /></aside> <Page /> </section> ); // remove all components matching react-redux pattern reactRemock.mock(/Connect\(\w\)/) // all any other container reactRemock.mock(/Container/)
这种方法有点粗鲁-会抹掉所有内容 ,使自己难以测试Contaiers,并且您可以使用更复杂的模拟来保留“第一个”:
import {createElement, remock} from 'react-remock'; // initially "open" const ContainerCondition = React.createContext(true); reactRemock.mock(/Connect\(\w\)/, (type, props, children) => ( <ContainerCondition.Consumer> { opened => ( opened ? ( // "close" and render real component <ContainerCondition.Provider value={false}> {createElement(type, props, ...children)} <ContainerCondition.Provider> ) // it's "closed" : null )} </ContainerCondition.Consumer> )
指向此处:没有逻辑也没有Presentation,也没有容器-所有逻辑都在外部。
额外解决方案-单独的问题
您可以使用defaultProps
保持紧密耦合 ,并在测试中使这些道具无效。
const PageChrome = ({Content = Page, Aside = ASideContainer}) => ( <section> <aside><Aside/></aside> <Content/> </section> );
那呢
因此,我刚刚发布了一些方法来缩小任何组件的范围,并使它们更具可测试性。 从gearbox
取出一个gear
的简单方法。 一个简单的模式,使您的生活更轻松。
端到端测试非常棒,但是很难模拟某些条件,这些条件可能发生在深层嵌套的要素中并为它们做好了准备。 您必须具有单元测试才能模拟不同的场景。 您必须进行集成测试,以确保所有接线正确。
您知道,正如Dan在另一篇文章中所写:
例如,如果一个按钮可以处于5种不同状态之一(正常,活动,悬停,危险,禁用),则更新该按钮的代码必须对5×4 = 20种可能的过渡是正确的-或禁止其中某些过渡。 我们如何驯服可能状态的组合爆炸并使视觉输出可预测?
虽然这里正确的解决方案是状态机,但基本的要求是能够挑选单个原子或分子并对其进行操作-。
本文的要点
- 演示组件应仅包含其他演示组件。
- 容器就是树。 组件是树叶。
- 您不必总是不在Presentational容器中包含容器,而不必仅在测试中包含容器。
通过阅读中级文章 ,您可能会更深入地研究问题,但是在这里让我们跳过所有的困难。
PS:这是ru-habr文章habr版本的翻译 。