最近ReactJS v18发布了。我开始研究它的改进,并检查我的哪些项目可以从中受益。
在写这篇文章的时候,我维护的主要基于react的项目是shlink-web-client,所以我自然地创建了一个新的分支并开始了这个过程。
更新相当顺利,因为我所使用的react生态系统中的大多数其他依赖项都已经更新到支持v18。除了一个,enzyme,我用来单元测试反应组件的测试框架。
准确地说,我能够继续运行我的测试,但其中一些测试(那些使用mount ,而不是shallow )仍在用 "旧的 "React v17方法渲染组件,导致它们的行为与该版本相同,并在控制台中打印出一个警告。
第一次修复尝试
enzyme与你所使用的react版本集成的方式是通过一个适配器库。最后一个官方的只支持React v16,但有人发布了一个适用于v17的非官方适配器,由于每周有接近65万的下载量,很快就成为了一种方式。
我检查了一下是否有支持React 18的,但很遗憾没有。
另外,v17的适配器的作者发表了一篇文章,表示酶已经死了,他不打算为v18发布一个,为v17发布一个是个错误。
考虑到这些事实,我开始面对可能是迁移到反应测试库的时刻。
迁移策略
我决定采取类似于上面文章中建议的迁移策略,但做了一些修改。
- 我无论如何都要更新到React 18,因为测试仍然通过。
- 立即迁移所有使用
mount的测试,因为那些测试的行为是不同的。 - 新的测试应该直接用react测试库(RTL从现在开始)编写。
- 当一个现有的测试需要改变时,考虑将其迁移到RTL,如果有时间的话,就这样做。
- 不时地推出一些PR,只是迁移少量的测试。
- 一旦所有的东西都迁移了,就把所有与酶有关的东西处理掉。
第一次接触和印象
我对RTL有点怀疑,因为它更注重集成测试,而不是单元测试,这意味着我必须完全改变写测试的思路。
它也不支持浅层渲染,这意味着在测试类似容器的组件时,你有时会渲染一个更大的组件树,可能会造成副作用,影响你的测试。
另一方面,越来越多的人指出,浅层渲染是一种反模式,这可能是酶被 "停用 "的原因。
另外,RTL已经成为官方推荐的测试react组件的框架,它已经变得非常流行,它甚至已经加入了一个更广泛的测试库组织下的 @testing-library组织。
事情是这样的,我开始使用它,很快我就对使用它的感觉和它给我的测试带来的所有好处感到惊讶。
我所经历的好处
一旦我开始迁移第一批测试,我就注意到一些好处。
- 改变我的思维方式实际上比我想象的要容易。文档写得非常好,所以开始使用RTL相对容易。
- 我的测试变得更简单了,而且不需要jest允许的那些讨厌的全局模拟(我在看着你。
jest.mock). - 测试与实现细节的耦合度降低了很多。我从测试组件的道具,转向测试用户在屏幕上看到的东西。
- 与上一点相关,我开始通过观察屏幕而不是组件的实现来写测试。
- 源代码的变化导致需要修改的测试减少。
- 一个渐进的迁移是可能的,一些测试仍然使用酶,而另一些已经在使用RTL。
总的来说,这感觉很好,我已经专门写了几个PR来迁移更多的测试。
我面临的挑战
当然,迁移到一个新的工具总是伴随着它自己的挑战,这也不例外。我将解释到目前为止我所面临的主要挑战以及我是如何解决这些问题的。
-
RTL让你用几种不同的方式来做一些事情,而文档中可能并不太清楚什么是推荐的选项。
为此,我推荐阅读RTL的作者Kent C. Dodds的这篇文章,其中解释了使用该库时的一些常见错误。
-
起初,我在异步副作用和组件的状态变化方面有些纠结,这很容易导致打印出类似
Warning: An update to MyComponent inside a test was not wrapped in act的警告。该警告还解释了你应该如何通过在
act()中包装你的代码来解决这个问题,但这个警告来自React,而不是RTL,而且它没有告诉你的是,RTL已经在act()中包装了所有需要的操作。这个错误通常意味着你没有等待事情的发生。这篇同样来自Kent C. Dodds的文章,解释了你需要知道的所有关于可能导致这种警告的事情,以及如何解决它们。
-
由于你从用户的角度开始测试,一些用例在不与实现细节耦合的情况下进行测试会很有挑战性。例如,当你有一个动态显示不同图像或图标的组件时,我曾经直接检查组件的属性。
为此,我发现最好的解决办法是使用jest snapshots。我不想手动检查哪个SVG在DOM中被渲染,只想知道在每种情况下使用的SVG是不同的。
// I went from this... test('...', () => { const wrapper = shallow(<MyComp dynamicValue="foo" />); expect(wrapper.find(FontAwesomeIcon).prop('icon')).toEqual(someIcon); }) // To this test('...', () => { const { container } = render(<MyComp dynamicValue="foo" />); expect(container.firstChild).toMatchSnapshot(); }) -
如果你使用一些渲染画布的库,也会发生非常类似的情况。没有办法从DOM的角度来测试,这就是RTL所做的。
在我的案例中,我使用Chart.js来渲染一些图表,所以我使用了jest-canvas-mock,它在canvas上公开了一个方法,以查看哪些是所有从库中调用的事件。
然后我再次使用快照来验证它们是否具有预期的 "形状"。
// I went from this... test('...', () => { const wrapper = shallow(<MyCompWithCanvas />); expect(wrapper.find(Chart).prop('data').datasets).toEqual(...); }) // To this test('...', () => { const { container } = render(<MyCompWithCanvas />); const events = container.querySelector('canvas')?.getContext('2d')?.__getEvents(); expect(events).toBeTruthy(); expect(events).toMatchSnapshot(); })
结论
不管是好是坏,酵素都不应再被认为是一种选择。如果你正在开始一个新的项目或在小项目中使用它,考虑尽快迁移到RTL。
如果你在更大的项目中使用它,按照本文描述的过程可能会帮助你。
在任何情况下,你都会得到惊喜。React测试库是一个伟大的工具,提供了比酶更多的好处。
它的主要问题是,它不是酶的直接替代品,所以你必须改变一下你的思维方式。