react单元测试
by Benedek Gagyi
通过Benedek Gagyi
查看这些针对React的初学者友好的单元测试模式 (Check out these beginner-friendly unit testing patterns for React)
There are many frameworks and even more ways to test a React component. But in my experience, there are a few patterns that are particularly useful to know, regardless of the testing framework you prefer (especially if you are just getting to know React). So here are 5 framework-agnostic unit testing patterns for React.
有很多框架以及更多测试React组件的方法。 但是根据我的经验,不管您喜欢哪种测试框架(特别是如果您只是了解React的时候),都有一些模式特别有用。 因此,这是针对React的5种与框架无关的单元测试模式。
Alex Moldovan published a great article a few days ago about the current patterns in React. The thing I liked about it the most is the way he approached the problem. You see, since React is so unopinionated, a universal style guide would not suit the community (we saw the exact opposite when John Papa published his style guide for AngularJS).
Alex Moldovan几天前发表了一篇很棒的文章,介绍了React的当前模式。 我最喜欢它的是他解决问题的方式。 您会看到,由于React并非毫无道理,因此通用样式指南不适合社区(当John Papa发布AngularJS样式指南时,情况恰恰相反)。
Alex solves this by offering patterns that are useful for beginners and more experienced developers alike without stating one “true” way of doing things.
Alex通过提供对初学者和经验丰富的开发人员都有用的模式来解决此问题,而没有说明一种“真实”的做事方式。
So as a continuation of his work, here are a few patterns I find useful when writing unit tests for React components.
因此,作为他的工作的延续,在为React组件编写单元测试时,我发现一些有用的模式。
First things first: what should I test?
首先,我应该测试什么?
Every time I teach the basics of unit testing, the same question gets asked: “What should I write my tests against? What are the cases my tests should check?” Giving a one-sentence answer that fits all the possible situations is quite hard, but in case of React it’s slightly easier.
每当我教授单元测试的基础知识时,都会问同样的问题:“我应该针对什么编写测试? 我的测试应检查哪些情况?” 给出一个适合所有可能情况的单句答案非常困难,但是在React的情况下要容易一些。
Most of the components you are going to work with will be stateless, so they can be viewed simply as pure functions: they get a few arguments as props and return a rendered component. So the answer in this case for the question above is: “Check if the different combinations of inputs result in correct outputs.” If you ask me, that’s a lot easier to wrap your head around and to execute!
您将使用的大多数组件都是无状态的,因此可以将它们简单地视为纯函数:它们将一些参数作为props并返回渲染的组件。 因此,在这种情况下,对于上述问题的答案是:“检查输入的不同组合是否导致正确的输出。” 如果您问我,这比将头缠绕和执行容易得多!
Just be careful: if you see this.state in a component, it means that it’s stateful. So on top of the things mentioned earlier, you’ll need to write tests against the state mutations, too.
请注意:如果在组件中看到this.state ,则表明它是有状态的。 因此,除了前面提到的内容之外,您还需要针对状态突变编写测试。
#1: PropTypes
#1:PropTypes
In most testing frameworks, a warning is thrown when a prop is not provided or has an incorrect type. For some reason, many people ignore these and let them pile up, rendering the PropTypes useless from the testing point of view.
在大多数测试框架中,当未提供道具或类型不正确时会引发警告。 由于某些原因,许多人忽略了这些,让它们堆积起来,从测试的角度来看,使PropTypes变得无用。
An argument in favor of doing this is that these errors will appear in the browser console as errors at runtime, so there’s no reason to provide all the needed props in the tests.
支持这样做的一个论点是,这些错误将在运行时作为错误显示在浏览器控制台中,因此没有理由在测试中提供所有必需的道具。
In my experience, while the above statement is true, proceeding this way has one major drawback: some errors and bugs can only be found at runtime and not by running the tests. And that’s one of the reasons we write unit tests: so we don’t have to check everything manually at runtime.
以我的经验,虽然上面的陈述是正确的,但是以这种方式进行操作存在一个主要缺点:有些错误和错误只能在运行时发现,而不能通过运行测试来发现。 这就是我们编写单元测试的原因之一:因此,我们不必在运行时手动检查所有内容。
If you are using a type system like TypeScript or Flow, this may not apply to you, since in that case an error will be thrown at compile time. This is a big advantage and should be considered when deciding upon using such tools. Thanks to Liran Tal, who pointed this out!
如果您使用的是TypeScript或Flow之类的类型系统,则可能不适用于您,因为在这种情况下,编译时会引发错误。 这是一个很大的优势,在决定使用此类工具时应予以考虑。 感谢Liran Tal ,他指出了这一点!
Omitting to set a prop in a unit test is not a bug in our application, so why should we bother fulfilling the PropTypes contract correctly? The answer is simple: because it helps to keep the tests healthy. Adding a prop in a component will result in a PropType warning in the tests, thus warning us that our tests don’t cover every case. The same goes for changing the type of a PropType: if a warning is thrown in our tests, it means that they need updating.
省略在单元测试中设置道具不是我们应用程序中的错误,那么为什么我们要麻烦正确地履行PropTypes合同呢? 答案很简单:因为它有助于保持测试的健康。 在组件中添加道具会在测试中产生PropType警告,从而警告我们我们的测试不能涵盖所有情况。 更改PropType的类型也是如此:如果在我们的测试中引发警告,则意味着它们需要更新。
The only drawback of this method is that the props in the test need to be kept up to date. Next, I’ll discuss the way I prefer to do it.
这种方法的唯一缺点是测试中的道具需要保持最新。 接下来,我将讨论我更喜欢这样做的方式。
#2: Reusable props
#2:可重复使用的道具
When testing a React component, most of your test cases will need a certain set of props to hydrate the component with. The most straightforward way to do this is to create a constant for each test case containing a version of the props needed.
测试React组件时,大多数测试用例都需要一组特定的道具来与组件水合。 最简单的方法是为每个需要包含道具版本的测试用例创建一个常量。
The problem with this solution is that there are rarely any cases when all the props provided are relevant to that given test case. Most of the time they are just there to secure the correct behavior of the component (and to fulfill the PropTypes).
该解决方案的问题在于,几乎没有任何情况下提供的所有道具都与给定的测试用例相关。 大多数时候,它们只是在那儿确保组件的正确行为(并实现PropType)。
Copying and pasting these extra props is cumbersome, error prone, and results in a bloated code that’s hard to read.
复制并粘贴这些额外的道具很麻烦,容易出错,并且导致code肿的代码难以阅读。
The pattern I prefer to use to avoid this is based on having a global props constant, and extending it where necessary using the spread operator.
我更喜欢使用以避免这种情况的模式是基于具有全局props常数,并在必要时使用散布运算符对其进行扩展。
This pattern has three main benefits:
这种模式具有三个主要优点:
- no copy-pasting is needed, the code is more concise, and there’s no bloat 无需复制粘贴,代码更简洁,没有膨胀
- the PropTypes are always fulfilled correctly PropTypes始终正确满足
- the readability of the tests increases, since the props being used by the given test case are highlighted. It’s enough just to look at the definition of the props to see which case is being tested there. This is why I like to redefine a property if it’s used in that test case, even if it has the same or similar value in the global props object. 由于突出显示了给定测试用例所使用的道具,因此提高了测试的可读性。 仅查看道具的定义就可以看到正在测试哪种情况就足够了。 这就是为什么我喜欢重新定义在测试用例中使用的属性,即使该属性在全局props对象中具有相同或相似的值也是如此。
A variation of this pattern also uses a global props object. But instead of always creating a new object for each test case, it keeps mutating the global object to fit the current needs. I find this solution too fragile for my taste. But my biggest issue with it is that it causes tests to depend on each other, resulting in severe performance problems, since these tests can’t be run in parallel.
这种模式的变体还使用了全局props对象。 但是,它不会总是为每个测试用例创建一个新对象,而是会不断更改全局对象以适应当前需求。 我觉得这个解决方案对我来说太脆弱了。 但是我最大的问题是,它导致测试相互依赖,导致严重的性能问题,因为这些测试不能并行运行。
#3: Shallow rendering
#3:浅层渲染
A challenge of unit testing in general is writing code that tests the given unit, but not its dependencies. Just like in any decent ecosystem, we have a ton of options when we want to mock or stub the dependencies of a React component.
通常,单元测试的一个挑战是编写代码来测试给定的单元,而不是测试其依赖项。 就像在任何体面的生态系统中一样,当我们要模拟或存根React组件的依赖项时,我们有很多选择。
One of these libraries uses such a simple, yet elegant solution that I think should be part of all of your React unit tests. This library is called Enzyme. It’s made by the nice people over at AirbnbEng, and the feature I’m talking about is shallow rendering.
这些库之一使用了这样一种简单而优雅的解决方案,我认为它应该成为所有React单元测试的一部分。 该库称为酶 。 它是由AirbnbEng的好心人制作的 ,而我正在谈论的功能是浅层渲染。
Shallow rendering is basically a way to render a React component without rendering its subcomponents, thus making the test independent from these subcomponents.
浅层渲染基本上是一种渲染React组件而不渲染其子组件的方法,从而使测试独立于这些子组件。
Let me give you an example. Let’s say we have a component called TextAndButton that has a child component called Button:
让我举一个例子。 假设我们有一个名为TextAndButton的组件,该组件有一个名为Button的子组件:
When TextAndButton is rendered in a browser, its subcomponent is rendered also. So in the end it will look something like this:
在浏览器中呈现TextAndButton时, 还将呈现其子组件。 因此,最终它将看起来像这样:
But when you do a shallow rendering, the subcomponents remain just as they were written:
但是,当您进行浅层渲染时,子组件将保持原样:
This way if Button has any dependencies, it’s not dragged in for this rendering. Even better: the parent component doesn’t need to know anything about its children.
这样,如果Button具有任何依赖关系,则不会将其拖入此渲染中。 更好的是:父组件不需要了解有关其子组件的任何信息。
Besides the clear performance benefits, I like this approach because it simplifies the mental model and makes it clear what should be tested where.
除了明显的性能优势外,我喜欢这种方法,因为它简化了心理模型,并明确了应该在哪里测试什么。
In the case of our example, the test for TextAndButton should only check if the correct action was passed to Button. But we don’t care about anything it actually does, like displaying the correct text or actually calling the provided function when needed.
在我们的示例中, TextAndButton的测试应该 仅检查是否已将正确的操作传递给Button。 但是我们不关心它实际执行的任何操作,例如显示正确的文本或在需要时实际调用提供的函数。
In addition to that, your tests will be more robust, since changing a child component won’t break the tests written for the parent.
除此之外,您的测试将更加健壮,因为更改子组件不会破坏为父组件编写的测试。
While I can imagine a few instances where shallow rendering is not the best idea, in general I think it can and should be used for all of your component tests.
虽然我可以想象到一些情况下,浅层渲染不是最好的主意,但总的来说,我认为它可以并且应该用于所有组件测试。
#4: Redux Reducers and Action Creators
#4:Redux Reducer和动作创建者
The philosophy of React is heavily influenced by functional programming, and the same is true for Redux. So don’t get scared by all the fancy names like “reducer” and “action creator”: these are just regular, pure functions with specific purposes.
React的哲学在很大程度上受到函数式编程的影响,对于Redux来说也是如此。 因此,不要被“减速器”和“动作创建者”等所有奇特的名字所吓倒:它们只是具有特定用途的常规纯函数。
The popularity of Redux is in part due to its simplicity. That’s why it amazes me every time I see projects using overcomplicated reducer and action creator testing patterns.
Redux之所以受欢迎,部分原因是其简单性。 这就是为什么每次我看到使用过度复杂化的reducer和动作创建者测试模式的项目时都会感到惊讶的原因。
In my experience, unit testing the reducers and the action creators as functions is perfectly sufficient. Check if they return the correct value for the given input (for example, a reducer should return the correct state object for the given input state and action), and that’s it. Doing anything more, like simulating actions or actually dispatching them on a mock store is overkill — that way, you end up testing Redux itself (besides your unit).
以我的经验,对化简和动作创建者作为功能进行单元测试就足够了。 检查它们是否为给定输入返回正确的值(例如,减速器应为给定输入状态和操作返回正确的状态对象),仅此而已。 做更多的事情,例如模拟动作或在模拟商店中实际分发动作,都是过大的杀伤力–这样,您最终会测试Redux本身(除了您的单元之外)。
Don’t take this from me: the official Redux testing docs use the same simplistic, function-based approach.
别忘了我: 官方Redux测试文档使用相同的基于功能的简化方法。
On the other hand there’s nothing wrong with simulating actions if your goal is to create integration tests to find out if everything is wired up correctly. But it’s important to not mix unit and integration tests, so use this pattern accordingly.
另一方面,如果您的目标是创建集成测试以找出所有连接是否正确,则模拟操作没有任何问题。 但是不要混用单元测试和集成测试很重要,因此请相应地使用此模式。
#5: Don’t test the DOM
#5:不要测试DOM
In my experience, writing fragile unit tests is almost as bad as writing none. If a unit test breaks when it should not (when the actual behavior of the unit didn’t change), it eats up time until the developer either fixes the fragility (costing extra time and effort) or even worse just comments it out.
以我的经验,编写易碎的单元测试几乎与不编写单元测试一样糟糕。 如果单元测试在不应进行的时间内中断(当单元的实际行为没有改变时),它将消耗时间,直到开发人员修复脆弱性(花费额外的时间和精力),或更糟的是将其注释掉。
There are many ways one can write fragile tests for React components, but there’s one I saw multiple times that’s really easy to avoid: writing tests against the DOM.
有很多方法可以为React组件编写易碎的测试,但我看到有很多次确实很容易避免:针对DOM编写测试。
To give you a simple example, asserting against the DOM would be checking the number of children the given component has. The way I see it, this is not a useful test, since it won’t fail when we actually break the component (for example by switching a password field to a regular text input). But it will break when we make minor, non-breaking adjustments (like wrapping a text in a span tag).
举一个简单的例子,对DOM进行断言将检查给定组件具有的子代数。 以我的方式来看,这不是一个有用的测试,因为它在我们实际破坏组件时不会失败(例如,通过将密码字段切换为常规文本输入)。 但是,当我们进行较小的,不间断的调整时(如将文本包裹在span标签中),它会中断。
Another way to test the DOM is to query certain elements inside your component by their “heritage,” by going through “child-of-sibling-of-child-of” chains. How a given component is built up internally should not be tested because it changes too often and, more importantly, because it holds no additional value.
测试DOM的另一种方法是,通过“子级子级”链来通过其“遗传”查询组件中的某些元素。 不应测试给定组件在内部的构建方式,因为它变化太多,更重要的是,因为它没有任何附加价值。
Opt instead for focused, CSS-like queries that target only the element you are interested in. Again, a query like div span img is not a good idea. Go instead for class-based queries that don’t rely on the HTML structure.
取而代之的是,仅针对您感兴趣的元素进行集中式,类似CSS的查询。同样,像div span img这样的查询也不是一个好主意。 请改用不依赖HTML结构的基于类的查询。
Final thoughts
最后的想法
Testing React components, especially with the latest tools, is a breeze. Don’t get discouraged by the initial hardships and the few hours spent googling at the beginning. You’ll win back those hours tenfold if you keep writing good quality tests.
轻而易举地测试React组件,尤其是使用最新工具进行测试。 最初的艰辛和刚开始时花时间搜索的几个小时不要灰心。 如果您继续编写高质量的测试,那么您将赢得十倍的时间。
And don’t forget: you are not only helping your future self, but also your users, who’ll be thankful to receive the cool new features faster and bug free.
而且请不要忘记:您不仅在帮助自己的未来,而且还在帮助您的用户,他们将很高兴能更快地获得出色的新功能且无错误。
翻译自: https://www.freecodecamp.org/news/unit-testing-patterns-for-react-720a8275873b/
react单元测试