react测试组件

When I first started learning to test my apps back in the day, I would get very frustrated with the different types, styles and technologies used for testing, along with the disbanded array of blog posts, tutorials and articles. I found this to be true as well for React testing.

当我第一次开始学习测试我的应用程序的那一天时,我会对用于测试的不同类型,样式和技术以及无数的博客文章,教程和文章感到沮丧。 我发现这对于React测试也是正确的。

So I decided to just write a complete React testing guide in one article.

因此,我决定只写一篇完整的React测试指南。

Complete Guide, huh, are you going to cover every possible testing scenario? Of course not. However, it will be a complete foundational guide to testing and will be enough to build off of for most other edge cases.

完整指南,呵呵,您打算涵盖所有可能的测试方案吗? 当然不是。 但是,这将是测试的完整基础指南,并且足以在大多数其他极端情况下进行构建。

Also I have curated an extensive collection of blog posts, articles and tutorials in the further reading section at the end that should give you enough knowledge to be in the top 10% of developers in terms of testing.

另外,在最后的阅读部分中,我还整理了一系列博客文章,文章和教程,这些知识应该使您有足够的知识成为测试方面排名前10%的开发人员。

You can find the completed project here:

您可以在这里找到完成的项目:

https://github.com/iqbal125/react-hooks-testing-complete

https://github.com/iqbal125/react-hooks-testing-complete

目录 (Table of Contents)

Theory

理论

  • What is Testing?   什么是测试?
  • Why Test? 为什么要测试?
  • What to Test? 要测试什么?
  • What Not to Test? 什么不测试?
  • How I test我如何测试
  • Shallow vs Mount浅VS山
  • unit vs integration vs e to e单元vs集成vs e到e

Preliminary Info

初步资料

  • a few odds and ends几率

Enzyme

酵素

  • Enyme Setup酶设置
  • react-test-rendererReact测试渲染器
  • snapshot testing快照测试
  • testing implementation details测试实施细节

React Testing Library

React测试库

  • useState and props useState和道具
  • useReducer()useReducer()
  • useContext()useContext()
  • Controlled component Forms受控组件表格
  • useEffect() and Axios API requestsuseEffect()和Axios API请求

Cypress

  • A complete end to end test完整的端到端测试

Continuous Integration

持续集成

  • Travis.yml特拉维斯
  • Code Coverage with coveralls 工作服的代码覆盖率

理论 (Theory)

什么是测试? (What is testing?)

Let's start at the beginning and discuss what testing is. Testing is a 3 step process that looks like this:

让我们从头开始,讨论什么是测试。 测试是一个三步过程,如下所示:

Arrange, your app is in a certain original state. Act, then something happens (click event, input, etc.). Then you assert, or make a hypothesis, of the new state of your app. The tests will pass if your hypothesis is correct and fail if it is wrong.

安排您的应用处于原始状态。 采取行动,然后发生一些事情(单击事件,输入等)。 然后,您可以断言或假设您的应用程序的新状态。 如果您的假设正确,则测试将通过;如果假设错误,则测试将失败。

Unlike your react components, your tests are not executed in the browser. Jest is the test runner and testing framework used by React. Jest is the environment where all your tests are actually executed. This is why you do not need to import expect and describe into this file. These functions are already available globally in the jest environment.

与您的React组件不同,您的测试不在浏览器中执行。 Jest是React使用的测试运行程序和测试框架。 开玩笑是实际执行所有测试的环境。 这就是为什么您无需将expectdescribe导入此文件的原因。 这些功能已经在jest环境中全局可用。

Your tests syntax will look something like this:

您的测试语法如下所示:

describe('Testing sum', () => {function sum(a, b) {return a + b;}it('should equal 4',()=>{expect(sum(2,2)).toBe(4);})test('also should equal 4', () => {expect(sum(2,2)).toBe(4);})
});

describe wraps our it or test blocks, and is a way to group our tests. Both it and test are keywords and can be used interchangeably. The string will be something that should happen with your tests and will be printed to the console. toBe() is a matcher that works with expect to allow you to make assertions. There are many more matchers and global variables offered by jest, see the links below for a complete list.

describe包装我们的ittest块,并且是对我们的测试进行分组的一种方式。 ittest都是关键字,可以互换使用。 该字符串将随测试一起发生,并将输出到控制台。 toBe() 是一个匹配项,可以与您期望的断言一起使用。 jest提供了更多匹配器和全局变量,请参阅下面的链接以获取完整列表。

https://jestjs.io/docs/en/using-matchers

https://jestjs.io/docs/en/using-matchers

https://jestjs.io/docs/en/api

https://jestjs.io/docs/zh/api

为什么要测试? (
Why test?)

Testing is done to ensure that your app will work as intended for your end users. Having tests will make your app more robust and less error prone. It is a way to verify that the code is doing what the developers intended.

测试已经完成,以确保您的应用能够按最终用户的预期运行。 进行测试将使您的应用程序更健壮,更不会出错。 这是一种验证代码是否符合开发人员预期的方式。

Potential Drawbacks:

潜在的缺点:

  • Writing tests is time consuming and difficult. 编写测试既耗时又困难。
  • In certain scenarios executing tests in CI can cost actual money. 在某些情况下,以CI执行测试可能会花费实际金钱。
  • If done incorrectly, it can give you false positives. Your tests pass, but your app doesn’t function as intended. 如果操作不正确,可能会给您带来误报。 您的测试通过了,但是您的应用无法正常运行。
  • Or false negatives. Your tests fail but your app is functioning as intended.还是假阴性。 测试失败,但您的应用程序运行正常。

要测试什么? (What to test?)

To build upon the previous point, Your tests should test the functionality of the app, that mimic how it will be used by your end users. This will give you confidence that your app will function as intended in your production environment.  We will of course go into much more detail through out this article but this is the basic gist of it.

为了建立上述观点,您的测试应测试该应用程序的功能,以模仿最终用户将如何使用该应用程序。 这将使您确信您的应用程序将在生产环境中按预期运行。 当然,我们将在本文中进行更详细的介绍,但这是本文的基本要旨。

什么不测试? (What not to test?)

I like to use Kent C dodds philosophy here that you shouldn’t test implementation details.

我喜欢在这里使用Kent C dodds哲学,因为您不应该测试实现细节。

Implementation details meaning testing things that are not end user functionality. We will see an example of this in the Enzyme section below.

实现细节意味着测试不是最终用户功能的事物。 我们将在下面的“酶”部分中看到一个示例。

It seems that you are testing functionality there but you are actually not. You are testing the name of the function. Because you can change the name of the function and your tests will break but your app will still work giving you a false negative.

似乎您正在那里测试功能,但实际上不是。 您正在测试函数的名称。 因为您可以更改函数的名称,并且测试将中断,但您的应用仍将运行,并给您带来假的否定。

Constantly having to worry about function and variable names is a headache, and having to rewrite tests every time you change them is tedious, I will show you a better approach.

经常需要担心函数和变量名是一件令人头疼的事,每次更改它们都必须重写测试是一件很麻烦的事情,我将向您展示一种更好的方法。

Const variables: these are unchanging variables, no need to test them.

常量变量:这些是不变的变量,无需测试。

Third party libraries: It is not your job to test these libraries. It is up to the creators of these libraries to test it. If you are not sure if a library is tested you should not use it. Or you can read the source code to see if the author included tests. You can download the source code and run these tests yourself. You can also ask the author if their library is production ready or not.

第三方库:测试这些库不是您的工作。 这些库的创建者可以对其进行测试。 如果不确定是否已测试库,则不应使用它。 或者,您可以阅读源代码以查看作者是否包含测试。 您可以下载源代码并自己运行这些测试。 您也可以询问作者,他们的库是否已准备好投入生产。

我个人的测试哲学 (My personal philosophy on testing)

A lot of my testing philosophy is based on Kent C dodds teachings so you will see a lot of his sentiments echoed here, but I some of my own thoughts as well.

我的很多测试哲学都是基于Kent C dodds的教so,因此您会在这里看到他的很多想法,但我也有自己的想法。

Many integration tests. No snapshot tests. Few unit tests. Few e to e tests.

许多集成测试。 没有快照测试。 很少的单元测试。 很少有e对e测试。

Unit testing is step above snapshot testing but its not ideal. It is however much easier to understand and maintain then snapshot testing.

单元测试是快照测试之上的步骤,但并不理想。 但是,与快照测试相比,它更容易理解和维护。

Write mostly integration tests. Unit tests are good but they don't really resemble the way your end user interacts with your app. It is very easy to test implementation details with unit tests, especially with shallow render.

主要编写集成测试。 单元测试虽然很好,但它们与最终用户与应用程序的交互方式并不相似。 使用单元测试来测试实现细节非常容易,尤其是使用浅渲染时。

Integration tests should mock as little as possible

集成测试应尽可能少地模拟

Do not test implementation details such as names of functions and variables.

不要测试实现细节,例如函数和变量的名称。

For example if we are testing a button and change the name of the function in the onClick method from increment() to handleClick() our tests would break but our component will still function. This is bad practice because we are basically just testing the name of the function which is an implementation detail, which our end user does not care about.

例如,如果我们正在测试一个按钮,并将onClick方法中的函数名称从increment()更改为handleClick(),我们的测试将失败,但我们的组件仍将起作用。 这是不好的做法,因为我们基本上只是测试功能的名称,而该功能的名称是实现细节,我们的最终用户并不关心。

浅vs坐骑 (Shallow vs mount)

Mount actually executes the html, css and js code like a browser would, but does so in a simulated way. It is “headless” for example, meaning it doesn’t render or paint anything to a UI, but acts as a simulated web browser and executes the code in the background.

Mount实际上像浏览器一样执行html,css和js代码,但是以模拟方式执行。 例如,它是“无头的”,这意味着它不会向UI渲染或绘制任何内容,而是充当模拟的Web浏览器并在后台执行代码。

Not spending time painting anything to the UI makes your tests much faster. However mount tests are still much slower than shallow tests.

不用花时间在UI上画任何东西,可以使测试更快。 但是,安装测试仍然比浅层测试慢得多。

This is why you unmount or cleanup  the component after each test, because it’s almost a live app and one test will affect another test.

这就是为什么每次测试后都要卸载或清理组件的原因,因为它几乎是一个实时应用程序,一个测试会影响另一个测试。

Mount/render is typically used for integration testing and shallow is used for unit testing.

安装/渲染通常用于集成测试,而浅层则用于单元测试。

shallow rendering only renders the single component we are testing. It does not render child components. This allows us to test our component in isolation.

浅层渲染仅渲染我们正在测试的单个组件。 它不呈现子组件。 这使我们能够独立测试组件。

For example consider this child and parent component.

例如,考虑此子级和父级组件。

import React from 'react';const App = () => {return (<div> <ChildComponent /> </div> )
}const ChildComponent = () => {return (<div><p> Child components</p></div>)
}

If we used shallow rendering of App.js we would get something like this, notice none of the DOM nodes for the child component are present, hence the term shallow render.

如果我们使用App.js浅表呈现,我们将得到类似的结果,请注意,子组件的DOM节点均不存在,因此称为浅表呈现。

<App><div> <ChildComponent /> </div>
</App>

Now we can compare this to mounting the component:

现在,我们可以将其与安装组件进行比较:

<App><div> <ChildComponent> <div><p> Child components</p></div></ChildComponent></div>
</App>

What we have above is much closer to what our app will look like in the browser, hence the superiority of mount/render.

上面的内容与我们的应用程序在浏览器中的外观非常接近,因此具有挂载/渲染的优越性。

单元vs集成vs端到端 (unit vs integration vs end to end)

unit testing: testing an isolated part of your app, usually done in combination with shallow rendering. example: a component renders with the default props.

单元测试 :测试应用程序的孤立部分,通常与浅层渲染结合进行。 示例:使用默认道具渲染的组件。

integration testing: testing if different parts work or integrate with each other. Usually done with mounting or rendering a component. example: test if a child component can update context state in a parent.

集成测试:测试不同部分是否工作或彼此集成。 通常通过安装或渲染组件来完成。 示例:测试子组件是否可以更新父组件中的上下文状态。

e to e testing: Stands for end to end. Usually a multi step test combining multiple unit and integration tests into one big test. Usually very little is mocked or stubbed. Tests are done in a simulated browser, there may or may not be a UI while the test is running. example: testing an entire authentication flow.

e to e测试 :代表端到端。 通常是将多个单元测试和集成测试组合成一个大型测试的多步骤测试。 通常很少有人嘲笑或存根。 测试是在模拟浏览器中完成的,测试运行时可能没有UI。 示例:测试整个身份验证流程。

初步资料 (Preliminary Info)

react-testing-library: I personally like to use react-testing-library but the common way is to use Enzyme. I will show you one example of Enzyme because it is important to be aware of Enzyme at a basic level and the rest of the examples with react-testing-library.

react-testing-library:我个人喜欢使用react-testing-library,但是常见的方法是使用Enzyme。 我将向您展示一个酶的示例,因为从基本的角度了解酶很重要,其余的示例都带有react-testing-library。

Examples Outline: Our examples will follow a pattern. I will first show you the React component and then the tests for it, with detailed explanations of each. You can also follow along with the repo linked at the beginning.

示例概述:我们的示例将遵循一个模式。 我将首先向您展示React组件,然后是它的测试,以及每个组件的详细说明。 您还可以按照开头的回购链接进行操作。

Configuration: I will also assume you are using create-react-app with the default testing setup with jest so I will skip manual configurations.

配置:我还将假设您正在使用带有默认测试设置的create-react-app和jest,因此我将跳过手动配置。

Sinon, mocha, chai: A lot of the functionality offered by sinon is available by default with jest so you dont need sinon. Mocha and chai are a replacement for jest. Jest comes pre configured out of the box to work with your app, so it doesnt make sense to use Mocha and chai.

Sinon,mocha,chai:默认情况下,jest提供了sinon提供的许多功能,因此您不需要sinon。 摩卡咖啡和柴是开玩笑的替代品。 Jest是开箱即用的预配置产品,可以与您的应用程序一起使用,因此使用Mocha和chai没有任何意义。

Components Naming scheme: My naming scheme for the components is <TestSomething /> but that does not mean they are fake components in any way. They are regular React components, this is just the naming scheme.

组件命名方案:我对组件的命名方案是<TestSomething />但这并不意味着它们在任何方面都是伪造的组件。 它们是常规的React组件,这只是命名方案。

npm test and jest watch mode: yarn test   worked for me. npm test did not work correctly with jest watch mode.

npm测试和开玩笑的观看模式yarn test对我有用 。 在开玩笑的观看模式下, npm test无法正常工作。

testing a single file: yarn test name of file

测试单个文件: yarn test文件名

React Hooks vs Classes: I use React Hooks components for most of the examples but due to the power of react-testing-library all these tests will directly work with class components as well.

React Hooks与类:我在大多数示例中都使用React Hooks组件,但是由于react-testing-library的强大功能,所有这些测试也可以直接用于类组件。

With the preliminary background info out of the way we can go over some code.

通过初步的背景信息,我们可以遍历一些代码。

酵素 (Enzyme)

酶设定 (Enzyme Setup)

Our third party libraries

我们的第三方图书馆

npm install enzyme enzyme-to-json  enzyme-adapter-react-16

npm install enzyme enzyme-to-json enzyme-adapter-react-16

Lets first start with our imports

首先让我们开始进口

import React from 'react';
import ReactDOM from 'react-dom';
import Basic from '../basic_test';import Enzyme, { shallow, render, mount } from 'enzyme';
import toJson from 'enzyme-to-json';
import Adapter from 'enzyme-adapter-react-16';Enzyme.configure({ adapter: new Adapter() })

We will start with our basic imports Our first 3 imports are for react and our component.

我们将从基本导入开始。我们的前3个导入用于react和我们的组件。

After this we import Enzyme. Then we import the toJson function from the 'enzyme-to-json' library. We will need this to convert our shallow rendered component into JSON which can be saved to the snapshot file.

在此之后,我们导入酶。 然后,我们从'enzyme-to-json'库导入toJson函数。 我们将需要使用它来将浅层呈现的组件转换为可保存到快照文件的JSON。

Finally we import our Adapter to make enzyme work with react 16 and initialize it as shown above.

最后,我们导入适配器以使酶与React16协同工作,并如上所述对其进行初始化。

react-test-renderer

React测试渲染器

React actually comes with its own test renderer you can use instead of enzyme and the syntax will look like this.

React实际上带有它自己的测试渲染器,您可以使用它代替酶,并且语法看起来像这样。

// import TestRenderer from 'react-test-renderer';
// import ShallowRenderer from 'react-test-renderer/shallow';// Basic Test with React-test-renderer
// it('renders correctly react-test-renderer', () => {
//   const renderer = new ShallowRenderer();
//   renderer.render(<Basic />);
//   const result = renderer.getRenderOutput();
//
//   expect(result).toMatchSnapshot();
// });

But even the react-test-render docs suggest using enzyme instead because it has a slightly nicer syntax and does the same thing. Just something to be aware of.

但是即使是react-test-render文档也建议使用酶来代替,因为它的语法略好,并且做同样的事情。 只是要注意的事情。

SnapShot测试 (SnapShot Testing)

Now our first test which is a snapshot test

现在我们的第一个测试是快照测试

it('renders correctly enzyme', () => {const wrapper = shallow(<Basic />)expect(toJson(wrapper)).toMatchSnapshot();
});

If you have not ran this command before, a __snapshots__ folder and test.js.snap file will be created for you automatically. On every subsequent test the new snapshot will be compared to the existing snapshot file. The test will pass if the snapshot has not changed and fail if it has changed.

如果您之前没有运行过此命令,将自动为您创建__snapshots__文件夹和test.js.snap文件。 在每个后续测试中,新快照将与现有快照文件进行比较。 如果快照未更改,则测试将通过;如果快照已更改,则测试将失败。

So essentially snapshot testing allows you to see how your component has changed since the last test, line for line. The lines of code that have changed is known as the diff.

因此从本质上讲,快照测试使您可以查看自上次测试以来一行一行的组件变化。 已更改的代码行称为diff。

Here is our basic component we are snapshot testing:

这是我们要进行快照测试的基本组件:

import React from 'react';const Basic = () => {return (<div ><h1> Basic Test</h1><p> This is a basic Test Component</p></div>);
}export default Basic;

Running the above test will generate a file that will look like this. This is essentially our tree of React DOM nodes.

运行上面的测试将生成一个看起来像这样的文件。 本质上,这是我们的React DOM节点树。

// Jest Snapshot v1, https://goo.gl/fbAQLPexports[`renders correctly enzyme 1`] = `
<div><h1>Basic Test</h1><p>This is a basic Test Component</p>
</div>
`;

And will produce a folder structure that will look like this:

并将产生一个如下所示的文件夹结构:

Your terminal output will look like this:

您的终端输出将如下所示:

However what happens if we changed our basic component to this

但是,如果我们将基本组件更改为此

import React from 'react';const Basic = () => {return (<div ><h1> Basic Test</h1></div>);
}export default Basic;

Our snapshots will now fail

我们的快照现在将失败

And will also give us the diff

而且也会给我们带来差异

Just like in git the " - " before each line means it was removed.

就像在git中,每行之前的“-”表示已将其删除。

We just need to press "w" to activate watch mode then press "u" to update the snapshot.

我们只需要按“ w”激活监视模式,然后按“ u”更新快照。

our snap shot file will be automatically updated with the new snapshot and will pass our tests

我们的快照文件将使用新快照自动更新,并通过测试

// Jest Snapshot v1, https://goo.gl/fbAQLPexports[`renders correctly enzyme 1`] = `
<div><h1>Basic Test</h1>
</div>
`;

This is it for snapshot testing but if you read my personal thoughts section you know I dont snapshot test. I included it here because like Enzyme it is very common and something you should be aware of, but below I'll try to explain why I dont use it.

这是用于快照测试的,但是如果您阅读我的个人想法部分,便知道我不进行快照测试。 我将其包含在此处,是因为它像酶一样非常常见,您应该注意这一点,但是下面我将尝试解释为什么我不使用它。

Let's go over again what snapshot testing is. It essentially allows you to see how your component has changed since the last test. What are the benefits of this.

让我们再来看看什么是快照测试。 从本质上讲,它使您可以查看自上次测试以来组件的变化。 这有什么好处。

  • Its very quick and easy to implement and sometimes requires only a few lines of code. 它非常容易实现,有时只需要几行代码。
  • You can see if our component is rendering correctly. You can see the DOM nodes clearly with the .debug() function.

    您可以查看我们的组件是否正确渲染。 您可以使用.debug()函数清楚地看到DOM节点。

    You can see if our component is rendering correctly. You can see the DOM nodes clearly with the .debug() function.

    您可以查看我们的组件是否正确渲染。 您可以使用.debug()函数清楚地看到DOM节点。

Cons, Arguments against snapshot testing:

缺点,反对快照测试的论点:

  • The only thing a snapshot test does is tell you whether the syntax of your code has changed since the last test. 快照测试唯一要做的就是告诉您自上次测试以来代码的语法是否已更改。
  • So what is it really testing? Some would argue not much.那么到底是什么测试呢? 有人会争论不大。
  • Also basic rendering of the app correctly is React’s job so you're going a little into testing a third party library territory. 应用程序的基本呈现也是正确的,这是React的工作,因此您将花一点时间来测试第三方库领域。
  • Also comparing diffs can be done with git version control. This should not be the job of snapshot testing.也可以使用git版本控制比较差异。 这不应该是快照测试的工作。
  • A failed test doesn’t mean your app isn’t working as intended, only that your code has changed since the last time you ran the test. This can lead to a lot of false negatives and a lack of trust in the test. This can also lead to people just updating the test without looking too closely at it. 测试失败并不意味着您的应用程序未按预期运行,仅表示自上次运行测试以来您的代码已更改。 这可能会导致很多误报和对测试的不信任。 这也可能导致人们只是更新测试而没有仔细查看它。
  • Snapshot testing also tells you if your JSX is syntactically correct, but again this can be easily done in the dev environment. Running a snapshot test just to check syntax errors doesnt make any sense. 快照测试还会告诉您JSX在语法上是否正确,但是同样可以在开发环境中轻松完成。 运行快照测试只是为了检查语法错误没有任何意义。
  • It can become hard to understand what’s happening in a Snapshot test, since most people use snapshot testing with shallow rendering, which doesnt render child components so it doesnt give the developer any insights at all.

    很难理解Snapshot测试中发生的情况,因为大多数人将快照测试与浅层渲染一起使用,浅层渲染不会渲染子组件,因此根本无法为开发人员提供任何见识。

See the further reading section for more info

请参阅进一步阅读部分以获取更多信息

使用酶测试实施细节 (Testing Implementation details with Enzyme)

Here I will give an example on why not to test implementation details. Say we have simple counter component like so:

在这里,我将举例说明为什么不测试实现细节。 假设我们有简单的计数器组件,例如:

import React, { Component } from 'react';class Counter extends Component {constructor(props) {super(props)this.state = {count: 0}}increment = () => {this.setState({count: this.state.count + 1})}//This incorrect code will still cause tests to pass// <button onClick={this.incremen}>//   Clicked: {this.state.count}// </button>render() {return (<div><button className="counter-button" onClick={this.incremen}>Clicked: {this.state.count}</button></div>)}
}export default Counter;

You will notice I have a comment suggesting that a non-working app will still cause the tests to pass, for example by misspelling the name of the function in the onClick event.

您会注意到我有一条评论,建议不起作用的应用程序仍会导致测试通过,例如,在onClick事件中拼写错误的函数名称。

And let's see the tests which will make it clear why.

让我们看看可以清楚说明原因的测试。

import React from 'react';
import ReactDOM from 'react-dom';
import Counter from '../counter';import Enzyme, { shallow, render, mount } from 'enzyme';
import toJson from 'enzyme-to-json';
import Adapter from 'enzyme-adapter-react-16';Enzyme.configure({ adapter: new Adapter() })// incorrect function assignment in the onClick method
// will still pass the tests.test('the increment method increments count', () => {const wrapper = mount(<Counter />)expect(wrapper.instance().state.count).toBe(0)// wrapper.find('button.counter-button').simulate('click')// wrapper.setState({count: 1})wrapper.instance().increment()expect(wrapper.instance().state.count).toBe(1)
})

Running the above code will pass the tests. So will using wrapper.setState(). So we have passing tests with a non functional app. I dont know about you but this doesnt give me confidence that our app will function as intended for our end users.

运行上面的代码将通过测试。 因此,将使用wrapper.setState() 。 因此,我们已经通过了非功能性应用的测试。 我不了解您,但这并不能使我确信我们的应用程序将按最终用户的预期运行。

Simulating click on the button will not pass the tests but it might give us the opposite problem, a false negative. Say we want to change the styling on the button by declaring a new CSS class for it, a very common situation. Our tests will now fail because we cant find our button anymore but our app will still be working, giving us a false negative. This is also true whenever we change the names of our functions or state variables.

模拟单击按钮不会通过测试,但是可能会给我们带来相反的问题,即假阴性。 假设我们想通过为按钮声明一个新CSS类来更改按钮的样式,这是非常常见的情况。 我们的测试现在将失败,因为我们无法再找到按钮,但我们的应用仍将运行,从而给我们带来负面的负面印象。 每当我们更改函数或状态变量的名称时,也是如此。

Every time we want to change our function and CSS class names we have to rewrite our tests, a very inefficient and tedious process.

每次我们想要更改函数和CSS类名时,我们都必须重写测试,这是一个非常低效而乏味的过程。

So what can we do instead?

那我们该怎么办呢?

React测试库 (React-testing-library)

useState (useState)

From the react-testing-library docs we see that the main guiding principle is

从react-testing-library文档中,我们看到主要的指导原则是

The more your tests resemble the way your software is used the more confidence they can give you.

测试越类似于软件使用方式,它们就可以给您带来更大的信心。

We will keep this guiding principle in mind as we explore further with our tests.

在进一步测试时,我们会牢记这一指导原则。

Let's start with a basic React Hooks component and test the state and props.

让我们从一个基本的React Hooks组件开始,并测试状态和道具。

import React, { useState } from 'react';const TestHook = (props) => {const [state, setState] = useState("Initial State")const changeState = () => {setState("Initial State Changed")}const changeNameToSteve = () => {props.changeName()}return (<div><button onClick={changeState}>State Change Button</button><p>{state}</p><button onClick={changeNameToSteve}>Change Name</button><p>{props.name}</p></div>)
}export default TestHook;

Our props are coming from the root parent component

我们的道具来自根父组件

const App = () => {const [state, setState] = useState("Some Text")const [name, setName] = useState("Moe")...const changeName = () => {setName("Steve")}return (<div className="App"><Basic /><h1> Counter </h1><Counter /><h1> Basic Hook useState </h1><TestHook name={name} changeName={changeName}/>...

So keeping our guiding principle in mind, what will our tests look like?

因此,牢记我们的指导原则,我们的测试会是什么样?

The way our end user will use this app will be to: see some text on the UI, see the text in the button, then click on it, finally see some new text on UI.

我们的最终用户将使用此应用程序的方式是:在UI上看到一些文本,在按钮上看到文本,然后单击它,最后在UI上看到一些新文本。

This is how we will write our tests using the React testing library.

这就是我们将使用React测试库编写测试的方式。

Use this command to install react testing library.

使用此命令安装React测试库。

npm install @testing-library/react

npm install @testing-library/react

not

npm install react-testing-library

npm install react-testing-library

Now for our tests

现在进行测试

import React from 'react';
import ReactDOM from 'react-dom';
import TestHook from '../test_hook.js';
import {render, fireEvent, cleanup} from '@testing-library/react';
import App from '../../../App'afterEach(cleanup)it('Text in state is changed when button clicked', () => {const { getByText } = render(<TestHook />);expect(getByText(/Initial/i).textContent).toBe("Initial State")fireEvent.click(getByText("State Change Button"))expect(getByText(/Initial/i).textContent).toBe("Initial State Changed")})it('button click changes props', () => {const { getByText } = render(<App><TestHook /></App>)expect(getByText(/Moe/i).textContent).toBe("Moe")fireEvent.click(getByText("Change Name"))expect(getByText(/Steve/i).textContent).toBe("Steve")
})

We first start with our usual imports.

我们首先从通常的进口开始。

Next we have the afterEach(cleanup) function. Since we are not using shallow render we have to unmount or cleanup after every test. And this is exactly what this function is doing.

接下来,我们有afterEach(cleanup)函数。 由于我们没有使用浅色渲染,因此我们必须在每次测试后卸载或清理。 这正是此功能正在执行的操作。

getByText is the query method we get by using object destructuring on the value of the render function. There are several more query methods but this is the one you will want to use most of the time.

getByText是通过对render函数的值使用对象getByText来获取的查询方法。 还有其他几种查询方法,但这是您大多数时候要使用的查询方法。

To test our state notice we are not using any function names or the names of our state variables. We are keeping with our guiding principle and not testing implementation details. Since a user will see the text on the UI, this is how we will query the DOM nodes. We will also query the button this way and click it. Finally we will query the final state based on the text as well.

为了测试我们的状态通知,我们没有使用任何函数名称或状态变量的名称。 我们遵守我们的指导原则,而不是测试实施细节。 由于用户将在UI上看到文本,因此这就是我们查询DOM节点的方式。 我们还将以这种方式查询按钮并单击它。 最后,我们还将根据文本查询最终状态。

(/Initial/i) is a regex expression that returns the first node that at least contains the text "Initial".

(/Initial/i)是一个正则表达式,它返回至少包含文本“ Initial”的第一个节点。

We can do the same exact thing with props as well. Since the props are going to be changed in App.js we will need to render it along with our component. Like the previous example we are not using function and variable names. We are testing the same way a user would use our app and that is through the text they will see.

我们也可以用道具做同样的事情。 由于要在App.js更改道具App.js我们需要将其与组件一起渲染。 像前面的示例一样,我们不使用函数和变量名。 我们正在测试用户使用我们的应用程序的方式,即通过他们看到的文字进行测试。

Hopefully this gives you a good idea of how to test with the react-testing-library and the guiding principle, you generally want to use getByText most of the time. There are a few exceptions we will see as we continue further.

希望这可以使您对如何使用react-testing-library和指导原则进行测试有个好主意,通常您通常希望在大多数时间使用getByText 。 在继续进行下去时,我们会看到一些例外。

useReducer (useReducer)

Now we can test a component with the useReducer hook. We will of course need actions and reducers to work with our component so let's set them up like so:

现在,我们可以使用useReducer挂钩测试组件。 当然,我们将需要操作和简化程序来使用我们的组件,因此让我们像这样设置它们:

Our reducer

我们的减速器

import * as ACTIONS from './actions'export const initialState = {stateprop1: false,
}export const Reducer1 = (state = initialState, action) => {switch(action.type) {case "SUCCESS":return {...state,stateprop1: true,}case "FAILURE":return {...state,stateprop1: false,}default:return state}
}

And the actions:

动作:

export const SUCCESS = {type: 'SUCCESS'
}export const FAILURE = {type: 'FAILURE'
}

we will keep things simple and use actions instead of action creators.

我们将使事情变得简单,并使用动作而不是动作创建者。

And finally the component that will use these actions and reducers:

最后是将使用这些动作和减速器的组件:

import React, { useReducer } from 'react';
import * as ACTIONS from '../store/actions'
import * as Reducer from '../store/reducer'const TestHookReducer = () => {const [reducerState, dispatch] = useReducer(Reducer.Reducer1, Reducer.initialState)const dispatchActionSuccess = () => {dispatch(ACTIONS.SUCCESS)}const dispatchActionFailure = () => {dispatch(ACTIONS.FAILURE)}return (<div><div>{reducerState.stateprop1? <p>stateprop1 is true</p>: <p>stateprop1 is false</p>}</div><button onClick={dispatchActionSuccess}>Dispatch Success</button></div>)
}export default TestHookReducer;

This is a simple component that will change stateprop1 from false to true by dispatching a SUCCESS action.

这是一个简单的组件,通过调度SUCCESS操作将stateprop1从false更改为true。

And now for our test.

现在进行测试。

import React from 'react';
import ReactDOM from 'react-dom';
import TestHookReducer from '../test_hook_reducer.js';
import {render, fireEvent, cleanup} from '@testing-library/react';
import * as Reducer from '../../store/reducer';
import * as ACTIONS from '../../store/actions';afterEach(cleanup)describe('test the reducer and actions', () => {it('should return the initial state', () => {expect(Reducer.initialState).toEqual({ stateprop1: false })})it('should change stateprop1 from false to true', () => {expect(Reducer.Reducer1(Reducer.initialState, ACTIONS.SUCCESS )).toEqual({ stateprop1: true  })})
})it('Reducer changes stateprop1 from false to true', () => {const { container, getByText } = render(<TestHookReducer />);expect(getByText(/stateprop1 is/i).textContent).toBe("stateprop1 is false")fireEvent.click(getByText("Dispatch Success"))expect(getByText(/stateprop1 is/i).textContent).toBe("stateprop1 is true")
})

We first start off by testing our reducer. And we can wrap the tests for the reducer in the describe block. These are fairly basic tests we are using to make sure the initial state is what we want and the actions produce the output we want.

我们首先通过测试减速器开始。 我们可以将reducer的测试包装在describe块中。 这些是相当基本的测试,我们正在使用这些测试来确保初始状态就是我们想要的状态,并且这些动作会产生我们想要的输出。

You can make an argument that testing the reducer is testing implementation details, but I found in practice that testing actions and reducers is one unit test that is always necessary.

您可以争论说测试化简器是测试实现细节,但是我发现在实践中测试动作和化简器是始终必需的一个单元测试。

This is a simple example so it doesn't seem like its a big deal but in larger more complex apps not testing reducers and actions can prove disastrous. So actions and reducers would be one exception to the testing implementation details rule.

这是一个简单的示例,因此似乎没什么大不了的,但是在更大,更复杂的应用程序中,不测试reducer和操作可能会造成灾难性的后果。 因此,操作和简化程序将是测试实现详细信息规则的一个例外。

Next we have our tests for the actual component. Notice again here we are not testing implementation details. We use the same pattern from the previous useState example we are getting our DOM nodes by the text and also finding and clicking the button with the text as well.

接下来,我们对实际组件进行测试。 再次注意,我们不在测试实现细节。 我们使用与上一个useState示例相同的模式,我们通过文本获取DOM节点,并且还找到并单击带有文本的按钮。

useContext (useContext)

Let's now move on and test if a child component can update the context state in a parent component. This may seem complex but it is rather simple and straight forward.

现在,让我们继续测试子组件是否可以更新父组件中的上下文状态。 这看似复杂,但相当简单直接。

We will first need our context object that we can initialize in its own file.

我们首先需要可以在其自己的文件中初始化的上下文对象。

import React from 'react';const Context = React.createContext()export default Context

We also need our parent app component which will hold the Context provider. The value passed down to the Provider will be the state value and the setState function of the App.js component.

我们还需要将包含Context提供程序的父应用程序组件。 传递给Provider的值将是App.js组件的状态值和setState函数。

import React, { useState } from 'react';
import TestHookContext from './components/react-testing-lib/test_hook_context';import Context from './components/store/context';const App = () => {const [state, setState] = useState("Some Text")const changeText = () => {setState("Some Other Text")}return (<div className="App"><h1> Basic Hook useContext</h1><Context.Provider value={{changeTextProp: changeText,stateProp: state}} ><TestHookContext /></Context.Provider></div>);
}export default App;

And for our component

对于我们的组件

import React, { useContext } from 'react';import Context from '../store/context';const TestHookContext = () => {const context = useContext(Context)return (<div><button onClick={context.changeTextProp}>Change Text</button><p>{context.stateProp}</p></div>)
}export default TestHookContext;

We have a simple component that displays the text we initialized in App.js and also we pass the setState function to the onClick method.

我们有一个简单的组件来显示在App.js初始化的文本,还将setState函数传递给onClick方法。

Note: The state is changed, initialized and contained in our App.js component. We have simply passed down the state value and setState function to our child component through context, but ultimately the state is handled in the App.js component. This will be important to understanding our test.

注意:状态已更改,初始化并包含在我们的App.js组件中。 我们只是通过上下文将状态值和setState函数传递给了子组件,但最终状态是在App.js组件中处理的。 这对于理解我们的测试很重要。

And our test:

和我们的测试:

import React from 'react';
import ReactDOM from 'react-dom';
import TestHookContext from '../test_hook_context.js';
import {act, render, fireEvent, cleanup} from '@testing-library/react';
import App from '../../../App'import Context from '../../store/context';afterEach(cleanup)it('Context value is updated by child component', () => {const { container, getByText } = render(<App><Context.Provider><TestHookContext /></Context.Provider></App>);expect(getByText(/Some/i).textContent).toBe("Some Text")fireEvent.click(getByText("Change Text"))expect(getByText(/Some/i).textContent).toBe("Some Other Text")
})

Even for context you can see we don't break our pattern of tests, we still find and simulate our events with the text.

即使就上下文而言,您也可以看到我们没有打破测试模式,但仍可以使用文本查找并模拟事件。

I have included the <Context.Provider/> and <TestHookContext /> components in the render function because it makes the code easier to read but we actually dont need either of them. Our test will still work if we passed only the <App /> component to the render function.

我在render函数中包含了<Context.Provider/><TestHookContext />组件,因为它使代码更易于阅读,但我们实际上不需要它们之一。 如果仅将<App />组件传递给render函数,我们的测试仍将有效。

const { container, getByText } = render(<App/>)

Why is this the case?

为什么会这样呢?

Let's think back to what we know about context. All the context state is handled in App.js, for this reason this is the main component we are actually testing, even though it seems like we are testing the child component that uses the useContext Hook. This code also works because of mount/render. As we know in shallow render the child components are not rendered, but in mount/render they are. Since <Context.Provider /> and <TestHookContext /> are both child components of <App /> they are rendered automatically.

让我们回想一下我们对上下文的了解。 所有上下文状态都在App.js处理, App.js ,这似乎是我们实际上正在测试的主要组件,尽管似乎我们正在测试使用useContext Hook的子组件。 由于mount / render,此代码也适用。 如我们所知,在浅渲染中,子组件没有渲染 ,但在安装/渲染中是。 由于<Context.Provider /><TestHookContext />都是<App />子组件,因此它们会自动呈现。

受控组件表格 (Controlled component Forms)

A controlled component form essentially means the form will work through the React state instead of the form maintaining its own state. Meaning the onChange handler will save the input text to the React state on every keystroke.

受控组件形式实质上意味着该形式将通过React状态工作,而不是保持其自身状态的形式。 意味着onChange处理程序将在每次击键时将输入文本保存为React状态。

Testing the form will be a little bit different than what we have seen so far, but we will try to still keep our guiding principle in mind.

测试表单与到目前为止所看到的稍有不同,但是我们将尽力记住我们的指导原则。

import React, { useState } from 'react';const HooksForm1 = () => {const [valueChange, setValueChange] = useState('')const [valueSubmit, setValueSubmit] = useState('')const handleChange = (event) => (setValueChange(event.target.value));const handleSubmit = (event) => {event.preventDefault();setValueSubmit(event.target.text1.value)};return (<div><h1> React Hooks Form </h1><form data-testid="form" onSubmit={handleSubmit}><label htmlFor="text1">Input Text:</label><input id="text1" onChange={handleChange} type="text" /><button type="submit">Submit</button></form><h3>React State:</h3><p>Change: {valueChange}</p><p>Submit Value: {valueSubmit}</p><br /></div>)
}export default HooksForm1;

This is a basic form we have here and we also display the value of the change and submit value in our JSX. We have the data-testid="form"  attribute which we will use in our test to the query for the form.

这是这里的基本形式,我们还显示更改的值并在JSX中提交值。 我们具有data-testid="form"属性,该属性将在测试中用于查询表单。

And our tests:

和我们的测试:

import React from 'react';
import ReactDOM from 'react-dom';
import HooksForm1 from '../test_hook_form.js';
import {render, fireEvent, cleanup} from '@testing-library/react';afterEach(cleanup)//testing a controlled component form.
it('Inputing text updates the state', () => {const { getByText, getByLabelText } = render(<HooksForm1 />);expect(getByText(/Change/i).textContent).toBe("Change: ")fireEvent.change(getByLabelText("Input Text:"), {target: {value: 'Text' } } )expect(getByText(/Change/i).textContent).not.toBe("Change: ")})it('submiting a form works correctly', () => {const { getByTestId, getByText } = render(<HooksForm1 />);expect(getByText(/Submit Value/i).textContent).toBe("Submit Value: ")fireEvent.submit(getByTestId("form"), {target: {text1: {value: 'Text' } } })expect(getByText(/Submit Value/i).textContent).not.toBe("Submit Value: ")})

Since an empty input element does not have text, we will use a getByLabelText() function to get the input node. This will still be keeping with our guiding principle, since the label text is what the user will read before inputting text.

由于空的输入元素没有文本,因此我们将使用getByLabelText()函数获取输入节点。 这仍然符合我们的指导原则,因为标签文本是用户在输入文本之前会阅读的内容。

Notice we will fire the .change() event instead of the usual .click() event. We also pass in dummy data in the form of:

请注意,我们将触发.change()事件,而不是通常的.click()事件。 我们还以以下形式传递虚拟数据:

{ target: { value: "Text" } }

{ target: { value: "Text" } }

Since the value from the form will be accessed in the form of event.target.value, this is what we pass to the simulated event.

由于将以event.target.value的形式访问表单中的值,因此我们将其传递给模拟事件。

Since we will generally not know what the text is the user will submit, we can just use a .not keyword to make sure the text has changed in our render method.

由于我们通常不知道用户将提交什么文本,因此我们可以使用.not关键字来确保文本在render方法中已更改。

We can test the submitting of the form in a similar way.  The only difference is we use the .submit() event and pass in dummy data in this way:

我们可以通过类似的方式测试表单的提交。 唯一的区别是我们使用.submit()事件,并以这种方式传递虚拟数据:

{ target: { text1: { value: 'Text' } } }

{ target: { text1: { value: 'Text' } } }

This is how to access form data from the synthetic event when a user submits a form. where text1 is the id of our input element. We will have to break our pattern a little bit here, and use the data-testid="form"   attribute to query for the form since there is really no other way to get the form.

当用户提交表单时,这是从综合事件访问表单数据的方法。 其中text1是我们输入元素的ID。 我们将不得不在这里稍微破坏一下模式,并使用data-testid="form"属性查询表单,因为实际上没有其他方法可以获取表单。

And thats it for the form. It isnt that different from our other examples. If you think you got it, let's move onto something a little more complex.

多数民众赞成在形式。 它与我们的其他示例没有区别。 如果您认为得到了,让我们继续进行一些复杂的工作。

Axios的useEffect和API请求 (useEffect and API requests with axios)

Let's now see how we would test the useEffect hook and API requests. This will be fairly different than what we have seen so far.

现在让我们看看如何测试useEffect挂钩和API请求。 这将与我们迄今为止所看到的完全不同。

Say we have a url passed down to a child component from the root parent.

假设我们有一个URL从根父级传递到子级组件。

...<TestAxios url='https://jsonplaceholder.typicode.com/posts/1' />...

And the component itself.

和组件本身。

import React, { useState, useEffect } from 'react';
import axios from 'axios';const TestAxios = (props) => {const [state, setState] = useState()useEffect(() => {axios.get(props.url).then(res => setState(res.data))}, [])return (<div><h1> Axios Test </h1>{state? <p data-testid="title">{state.title}</p>: <p>...Loading</p>}</div>)
}export default TestAxios;

We simply make an API request and save the results in the local state. We also use a ternary expression in our render method to wait until the request is complete to display the title data from json placeholder.

我们只需要发出一个API请求并将结果保存在本地状态即可。 我们还在render方法中使用三元表达式,直到请求完成以显示json占位符的标题数据为止。

You will notice we will again out of necessity have to make use of the data-testid attribute, and again it is an implementation detail since a user will not see or interact with this attribute in any way, but this is more realistic, since we will generally not know the text from a API request beforehand.

您会注意到,我们将不再需要使用data-testid属性,并且这也是实现细节,因为用户不会以任何方式看到或与该属性交互,但这是更现实的,因为我们通常不会事先知道API请求中的文本。

We will also be using mocks in this test.

我们还将在此测试中使用模拟。

A mock is way to simulate behavior we dont actually want to do in our tests. For example we mock API requests because we dont want to make real requests in our tests.

模拟是一种模拟我们在测试中实际上不想做的行为的方法。 例如,我们模拟API请求,因为我们不想在测试中提出真实的请求。

We dont want to make real API requests in our tests for various reasons: it will make our tests much slower, might give us a false negative, the API request will cost us money, or we will mess up our database with test data.

由于各种原因,我们不想在测试中发出真实的API请求:这会使我们的测试变慢得多,可能给我们带来假否定的结果,API请求会花费我们很多钱,或者我们将测试数据弄乱数据库。

import React from 'react';
import ReactDOM from 'react-dom';
import TestAxios from '../test_axios.js';
import {act, render, fireEvent, cleanup, waitForElement} from '@testing-library/react';import axiosMock from "axios";

We have our usual imports but you will notice something peculiar. We are importing axiosMock from the axios library. We are not importing a mock axios object from the axios library. We are actually mocking the axios library itself.

我们有通常的进口货,但您会发现一些奇怪的东西。 我们正在从axios库导入axiosMock 。 我们不是从axios库中导入模拟axios对象。 我们实际上是在模仿axios本身

How?

怎么样?

By using the mocking functionality offered by jest.

通过使用jest提供的模拟功能。

We first will make a __mocks__ folder adjacent to our test folder, so something like this.

我们首先将在测试文件夹旁边创建一个__mocks__文件夹,所以类似这样。

And inside the mocks folder we have an axios.js file and this is our fake axios library. And inside our fake axios library we have our jest mock function.

axios.js文件夹中,我们有一个axios.js文件,这是我们的假axios库。 在我们伪造的axios库中,我们有我们最好的模拟功能

Mock functions allow us to use functions in our jest environment without having to implement the actual logic of the function.

模拟功能使我们能够在开玩笑的环境中使用功能,而无需实现功能的实际逻辑。

So basically we are not going to implement the actual logic behind an axios get request. We will just use this mock function instead.

因此,基本上,我们不会在axios get请求后面实现实际的逻辑。 我们将只使用此模拟功能。

export default {get: jest.fn(() => Promise.resolve({ data: {} }) )
};

Here we have our fake get function. It is a simple function that is actually a JS object. get is our key and the value is the mock function. Like an axios API request, we resolve a promise. We wont pass in any data here, we will do that in our testing setup.

这是伪造的get函数。 这是一个简单的函数,实际上是一个JS对象。 get是我们的键,值是模拟函数 。 像axios API请求一样,我们可以解决承诺。 我们不会在此处传递任何数据,我们将在测试设置中进行传递。

Now our testing setup

现在我们的测试设置

//imports
...afterEach(cleanup)it('Async axios request works', async () => {axiosMock.get.mockResolvedValue({data: { title: 'some title' } })const url = 'https://jsonplaceholder.typicode.com/posts/1'const { getByText, getByTestId, rerender } = render(<TestAxios url={url} />);expect(getByText(/...Loading/i).textContent).toBe("...Loading")const resolvedEl = await waitForElement(() => getByTestId("title"));expect((resolvedEl).textContent).toBe("some title")expect(axiosMock.get).toHaveBeenCalledTimes(1);expect(axiosMock.get).toHaveBeenCalledWith(url);})

The first thing we do in our test is call our fake axios get request, and mock the resolved value with ironically the mockResolvedValue function offered by jest. This function does exactly what its name says, it resolves a promise with the data we pass in, which simulates what axios does.

在测试中,我们要做的第一件事是调用伪造的axios get request ,并讽刺地用jest提供的mockResolvedValue函数模拟解析后的值。 该函数完全按照其名称的含义进行操作,它使用我们传入的数据来解析承诺,从而模拟axios所做的事情。

This function has to be called before our render() function otherwise the test wont work. Because remember we are mocking the axios library itself. When our component runs the import axios from 'axios'; command it will be importing our fake axios library instead of the real one and this fake axios will be substituted in our component wherever we used axios.

必须在我们的render()函数之前调用此函数,否则测试将无法进行。 因为记住我们在嘲笑axios库本身。 当我们的组件import axios from 'axios';运行import axios from 'axios'; 命令,它将导入我们的假axios库,而不是真正的axios库 ,无论我们在哪里使用axios,此假axios都将替换为我们的组件。

Next we get our "...Loading" text node since this is what will be displayed before the promise resolves. After this we a function we havent seen before the waitForElement()  function, which will wait until the promise resolves before going to the next assertion.

接下来,我们得到“ ... Loading”文本节点,因为这是在诺言解决之前将显示的内容。 在此之后,我们没有在waitForElement()函数之前看到一个函数,该函数将等到promise解析后再进入下一个断言。

Also notice the await and async keywords, these are used in the exact same way as they are used in a non testing environment.

还要注意awaitasync关键字,它们的使用方式与非测试环境中使用的方式完全相同。

Once resolved the DOM node will have the text of "some title" which is the data we passed to our fake mock axios library.

解析后,DOM节点将具有“某些标题”的文本,这是我们传递到假模拟axios库的数据。

Next we make sure the request was only called once and with the right url. Even though we are testing the url we didnt make an API request with this url.

接下来,我们确保仅使用正确的URL调用一次请求。 即使我们正在测试该URL,我们也没有使用该URL发出API请求。

And this is it for API requests with axios. In the next section we will look at e to e tests with cypress.

这就是使用axios进行的API请求。 在下一部分中,我们将介绍使用赛普拉斯的e到e测试。

柏 (Cypress)

Lets now go over cypress which I believe is the best framework to run e to e tests. We are now longer in jest land, we will now be working solely with cypress which has its own testing environment and syntax.

现在让我们回顾一下赛普拉斯,我认为这是运行e到e测试的最佳框架。 现在,我们在玩笑的土地上待了更长的时间,现在,我们将仅与具有自己的测试环境和语法的赛普拉斯合作。

Cypress is pretty amazing and powerful. So amazing and powerful in fact that we can run every test we just went over in one test block and watch cypress run these tests in real time in a simulated browser.

赛普拉斯非常强大。 实际上,它是如此的强大和强大,我们可以在一个测试块中运行刚刚经过的每个测试,并观看赛普拉斯在模拟浏览器中实时运行这些测试。

Pretty cool, huh?

太酷了吧?

I think so. Anyway, before we can do that we need to setup cypress. Surprisingly Cypress can be installed as a regular npm module.

我认同。 无论如何,在我们可以这样做之前,我们需要设置柏树。 令人惊讶的是,赛普拉斯可以作为常规npm模块安装。

npm install cypress

npm install cypress

To run cypress you will need to use this command.

要运行cypress,您将需要使用此命令。

node_modules/.bin/cypress open

node_modules/.bin/cypress open

If that seems cumbersome to write every time you want open cypress so you can add it to your package.json.

如果每次要打开cypress时写起来都很麻烦,那么可以将其添加到package.json中。

..."scripts": {"start": "react-scripts start","build": "react-scripts build","test": "react-scripts test","eject": "react-scripts eject","cypress": "node_modules/.bin/cypress open", ...

this will allow you to open up cypress with just the npm run cypress command.

这将使您仅使用npm run cypress命令即可打开npm run cypress

Opening up cypress will give you a GUI that looks like this.

打开柏树将为您提供一个如下所示的GUI。

To actually run the cypress tests, your app will have to be running at the same time, which we will see in a second.

要实际运行cypress测试,您的应用程序必须同时运行,我们将在一秒钟内看到它。

Running the cypress open command will give you a basic configuration of cypress and create some files and folders for your automatically. A cypress folder will be created in the project root. We will write our code in the integration folder.

运行cypress open命令将为您提供cypress的基本配置,并自动创建一些文件和文件夹。 将在项目根目录中创建一个赛普拉斯文件夹。 我们将在集成文件夹中编写代码。

We can begin by deleting the examples folder. Unlike jest, cypress files take a .spec.js extension.  Because this is a e to e test we will run it on our main App.js file. So you should have a directory structure that now looks like this.

我们可以从删除示例文件夹开始。 与jest不同,cypress文件的扩展名为.spec.js 。 因为这是App.js测试,所以我们将在主App.js文件上运行它。 因此,您应该具有一个现在看起来像这样的目录结构。

We can also set a Base url in the cypress.json file. Just like this:

我们还可以在cypress.json文件中设置基本URL。 像这样:

{ "baseUrl": "http://localhost:3000" }

{ "baseUrl": " http://localhost:3000 " }

Now for our large monolithic test

现在进行我们的大型整体测试

import React from 'react';describe ('complete e to e test', () => {it('e to e test', () => {cy.visit('/')//counter testcy.contains("Clicked: 0").click()cy.contains("Clicked: 1")// basic hooks testcy.contains("Initial State")cy.contains("State Change Button").click()cy.contains("Initial State Changed")cy.contains("Moe")cy.contains("Change Name").click()cy.contains("Steve")//useReducer testcy.contains('stateprop1 is false')cy.contains('Dispatch Success').click()cy.contains('stateprop1 is true')//useContext testcy.contains("Some Text")cy.contains('Change Text').click()cy.contains("Some Other Text")//form testcy.get('#text1').type('New Text {enter}')cy.contains("Change: New Text")cy.contains("Submit Value: New Text")//axios testcy.request('https://jsonplaceholder.typicode.com/posts/1').should(res => {expect(res.body).not.to.be.nullcy.contains(res.body.title)})});
});

As mentioned we are running every single test we just went over in one test block. I have separated each section with a comment so it will easier to see.

如前所述,我们正在运行一个测试块中刚通过的每个测试。 我在每个部分都添加了注释,以便于查看。

Our test may look intimidating at first but most of the individual tests will follow a basic arrange-act-assert pattern.

首先,我们的测试可能看起来令人生畏,但大多数单个测试都将遵循基本的“行为—断言”模式。

cy.contains(Some innerHTML text of DOM node)cy.contains (text of button)
.click()cy.contains(Updated innerHTML text of DOM node)

Since this is a e to e test you will find no mocking at all. Our app will be running in its full development version in a simulated browser with a UI. This will be as close to testing our app in realistic way as we can get.

由于这是自动测试,因此您根本不会嘲笑。 我们的应用将在具有UI的模拟浏览器中以其完整开发版本运行。 这将尽可能接近以实际方式测试我们的应用程序。

Unlike unit and integration tests we do not need to explicitly assert some things. This is because some Cypress commands have built in default assertions. Default assertions are exactly what they sound like, they are asserted by default so no need to add a matcher.

与单元测试和集成测试不同,我们不需要明确声明某些内容。 这是因为某些赛普拉斯命令内置了默认断言。 默认断言与它们的听起来完全一样,它们是默认断言的,因此无需添加匹配器。

Cypress default assertions

赛普拉斯默认声明

Commands are chained together so order is important and one command will wait until a previous command is completed before running.

命令链接在一起,因此顺序很重要,一个命令将等到上一个命令完成后再运行。

Even when testing with cypress we will stick to our philosophy of not testing implementation details. In practice this is going to mean that we will not use html/css classes, ids or properties as selectors if we can help it. The only time we will need to use id is to get our form input element.

即使使用赛普拉斯进行测试,我们也将坚持不测试实现细节的理念。 实际上,这意味着如果我们可以帮助的话,我们将不使用html / css类,id或属性作为选择器。 我们唯一需要使用id的时间是获取表单输入元素。

We will make use of the cy.contains() command which will return a DOM node with matching text. Seeing and Interacting with text on the UI is what our end user will do, so testing this way will be in line with our guiding principle.

我们将使用cy.contains()命令,该命令将返回带有匹配文本的DOM节点。 最终用户将要做的是在UI上看到文本并与之交互,因此以这种方式进行测试将符合我们的指导原则。

Since we are not stubbing or mocking anything you will notice our tests will look very simplistic. This is good since this is a live running app, our tests will not have any artificial values.

由于我们没有存根或嘲笑任何东西,因此您会发现我们的测试看起来非常简单。 很好,因为这是一个实时运行的应用程序,所以我们的测试不会有任何人工值。

In our axios test we will make a real http request to our endpoint. Making a real http request in an e to e test is common. Then we will check to see if that value is not null. Then make sure that the data of the response appears in our UI.

在我们的axios测试中,我们将向端点发出一个实际的http请求。 在e-e测试中发出真正的http请求很常见。 然后,我们将检查该值是否不为null。 然后确保响应的数据出现在我们的UI中。

If done correctly you should see that cypress successfully ran the tests in chromium.

如果操作正确,您应该会看到柏树成功地通过了Chrome测试。

持续集成 (Continuous Integration)

Keeping track and Running all these tests manually can become tedious. So we have Continuous Integration, A way to automatically run our tests continuously.

跟踪并手动运行所有这些测试可能会变得乏味。 因此,我们有了持续集成,这是一种自动连续运行测试的方法。

特拉维斯CI (Travis CI)

To keep things simple we'll just use Travis CI for our Continuous integration. You should know though that there are much more complex CI setups using Docker and Jenkins.

为简单起见,我们将使用Travis CI进行持续集成。 您应该知道,尽管使用Docker和Jenkins的CI配置更为复杂。

You will need to sign up for a Travis and Github account, both of these are luckily free.

您将需要注册一个Travis和Github帐户,这两个都是免费的。

I would suggest just using the "Sign Up with Github" option on Travis CI.

我建议仅在Travis CI上使用“使用Github注册”选项。

Once there you can just go on your profile icon and click the slider button next to the repository you want CI on.

到达那里后,您可以直接进入个人资料图标,然后单击要配置项的存储库旁边的滑块按钮。

So that Travis CI knows what to do we will need to configure a .travis.yml file in our project root.

为了使Travis CI知道该怎么办,我们需要在项目根目录中配置.travis.yml文件。

language: node_jsnode_js: - stableinstall:- npm installscript:- npm run test- npm run coveralls

This essentially tells Travis that we are using node_js, download the stable version, install the dependencies and run the npm run test and npm run coveralls command.

这从本质上告诉Travis,我们正在使用node_js,下载稳定版本,安装依赖项并运行npm run test和npm run coveralls命令。

And this is it. You can know go on the dashboard and start the build. Travis will run the tests automatically and give you an output like this.  If your tests pass you are good to go. If they fail, your build will fail and you will need to fix your code and restart the build.

就是这样。 您可以知道在信息中心上并开始构建。 Travis将自动运行测试,并为您提供类似的输出。 如果您的考试通过了,那就太好了。 如果它们失败,则构建将失败,并且您将需要修复代码并重新启动构建。

工作服 (Coveralls)

coverall gives us a coverage report that essentially tells us how much of our code is being tested.

Coverall为我们提供了一份覆盖率报告,该报告实质上告诉我们正在测试多少代码。

You will need to sign up to coveralls and sync with your github account. Similar to Travis CI, just go to the add repos tab and turn on the repo that you also activated on Travis CI.

您将需要注册以使用所有功能并与您的github帐户同步。 与Travis CI相似,只需转到“添加存储库”选项卡并打开您也在Travis CI上激活的存储库即可。

Next go to your package.json file and add this line of code

接下来转到您的package.json文件,并添加以下代码行

"scripts": {"start": "react-scripts start","build": "react-scripts build","test": "react-scripts test --coverage","eject": "react-scripts eject","cypress": "node_modules/.bin/cypress open", "coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls"},

Be sure to add the --coverage flag to the react-scripts test command. This is what will generate the coverage data that coveralls will use to generate a coverage report.

确保将--coverage标志添加到react-scripts test命令中。 这就是生成覆盖率数据的方法,所有工作人员将使用这些数据来生成覆盖率报告。

And you can actually see this coverage data on the Travis CI console after your tests have ran.

运行测试后,您实际上可以在Travis CI控制台上看到此覆盖率数据。

Since we are not dealing with a private repo or Travis CI pro, we dont need to worry about repo tokens.

由于我们不与私人回购协议或Travis CI专业人士打交道,因此我们无需担心回购令牌。

Once your done you can add a badge to your repo README by copying the provided link on the dashboard.

完成后,您可以通过复制仪表板上提供的链接,将徽章添加到您的回购自述文件中。

It will look like this.

它看起来像这样。

结论 (Conclusion )

Count yourself among the top 20% of developers in terms of React testing skill if you made it through the entire tutorial.

如果您在整个教程中都达到了这一水平,那么就算自己在React测试技能方面名列前20%开发人员之列。

Thanks for reading. cheers.

谢谢阅读。 干杯。

You can follow me on twitter for more tutorials in the future: https://twitter.com/iqbal125sf?lang=en

以后您可以在Twitter上关注我,以获取更多教程: https : //twitter.com/iqbal125sf?lang=zh-CN

进一步阅读
(Further Reading)

Blog Posts:

博客文章:

https://djangostars.com/blog/what-and-how-to-test-with-enzyme-and-jest-full-instruction-on-react-component-testing/

https://djangostars.com/blog/what-and-how-to-test-with-enzyme-and-jest-full-instruction-on-react-component-testing/

https://engineering.ezcater.com/the-case-against-react-snapshot-testing

https://engineering.ezcater.com/the-case-against-react-snapshot-testing

https://medium.com/@tomgold_48918/why-i-stopped-using-snapshot-testing-with-jest-3279fe41ffb2

https://medium.com/@tomgold_48918/why-i-stopped-using-snapshot-testing-with-jest-3279fe41ffb2

https://circleci.com/blog/continuously-testing-react-applications-with-jest-and-enzyme/

https://circleci.com/blog/continuously-testing-react-applications-with-jest-and-enzyme/

https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html

https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html

https://willowtreeapps.com/ideas/best-practices-for-unit-testing-with-a-react-redux-approach

https://willowtreeapps.com/ideas/best-practices-for-unit-testing-with-a-react-redux-approach

https://blog.pragmatists.com/genuine-guide-to-testing-react-redux-applications-6f3265c11f63

https://blog.pragmatists.com/genuine-guide-to-testing-react-redux-applications-6f3265c11f63

https://hacks.mozilla.org/2018/04/testing-strategies-for-react-and-redux/

https://hacks.mozilla.org/2018/04/testing-strategies-for-react-and-redux/

https://codeburst.io/deliberate-practice-what-i-learned-from-reading-redux-mock-store-8d2d79a4b24d

https://codeburst.io/deliberate-practice-what-i-learned-from-reading-redux-mock-store-8d2d79a4b24d

https://www.robinwieruch.de/react-testing-tutorial/

https://www.robinwieruch.de/react-testing-tutorial/

https://medium.com/@ryandrewjohnson/unit-testing-components-using-reacts-new-context-api-4a5219f4b3fe

https://medium.com/@ryandrewjohnson/unit-testing-components-using-reacts-new-context-api-4a5219f4b3fe

Kent C dodds Posts on Testing

Kent C dodds Posts on Testing

https://kentcdodds.com/blog/introducing-the-react-testing-library

https://kentcdodds.com/blog/introducing-the-react-testing-library

https://kentcdodds.com/blog/unit-vs-integration-vs-e2e-tests

https://kentcdodds.com/blog/unit-vs-integration-vs-e2e-tests

https://kentcdodds.com/blog/why-i-never-use-shallow-rendering

https://kentcdodds.com/blog/why-i-never-use-shallow-rendering

https://kentcdodds.com/blog/demystifying-testing

https://kentcdodds.com/blog/demystifying-testing

https://kentcdodds.com/blog/effective-snapshot-testing

https://kentcdodds.com/blog/effective-snapshot-testing

https://kentcdodds.com/blog/testing-implementation-details

https://kentcdodds.com/blog/testing-implementation-details

https://kentcdodds.com/blog/common-testing-mistakes

https://kentcdodds.com/blog/common-testing-mistakes

https://kentcdodds.com/blog/ui-testing-myths

https://kentcdodds.com/blog/ui-testing-myths

https://kentcdodds.com/blog/why-youve-been-bad-about-testing

https://kentcdodds.com/blog/why-youve-been-bad-about-testing

https://kentcdodds.com/blog/the-merits-of-mocking

https://kentcdodds.com/blog/the-merits-of-mocking

https://kentcdodds.com/blog/how-to-know-what-to-test

https://kentcdodds.com/blog/how-to-know-what-to-test

https://kentcdodds.com/blog/avoid-the-test-user

https://kentcdodds.com/blog/avoid-the-test-user

Cheat Sheets / github threads

Cheat Sheets / github threads

https://devhints.io/enzyme

https://devhints.io/enzyme

https://devhints.io/jest

https://devhints.io /jest

https://github.com/ReactTraining/react-router/tree/master/packages/react-router/modules/__tests__

https://github.com/ReactTraining/react-router/tree/master/packages/react-router/modules/__tests__

https://github.com/airbnb/enzyme/issues/1938

https://github.com/airbnb/enzyme/issues/1938

https://gist.github.com/fokusferit/e4558d384e4e9cab95d04e5f35d4f913

https://gist.github.com/fokusferit/e4558d384e4e9cab95d04e5f35d4f913

https://airbnb.io/enzyme/docs/api/selector.html

https://airbnb.io/enzyme/docs/api/selector.html

Docs

Docs

https://docs.cypress.io

https://docs.cypress.io

https://airbnb.io/enzyme/

https://airbnb.io/enzyme/

https://github.com/dmitry-zaets/redux-mock-store

https://github.com/dmitry-zaets/redux-mock-store

https://jestjs.io/docs/en

https://jestjs.io/docs/en

https://testing-library.com/docs/learning

https://testing-library.com/docs/learning

https://sinonjs.org/releases/v7.3.2/

https://sinonjs.org/releases/v7.3.2/

https://redux.js.org/recipes/writing-tests

https://redux.js.org/recipes/writing-tests

https://jestjs.io/docs/en/using-matchers

https://jestjs.io/docs/en/using-matchers

https://jestjs.io/docs/en/api

https://jestjs.io/docs/en/api

翻译自: https://www.freecodecamp.org/news/testing-react-hooks/

react测试组件

react测试组件_如何测试React组件:完整指南相关推荐

  1. react 路由重定向_如何测试与测试库的路由器重定向React

    react 路由重定向 React testing-library is very convenient to test React components rendering from props, ...

  2. react 组件引用组件_自定位React组件

    react 组件引用组件 While React has ways to break the hatch and directly manipulate the DOM there are very ...

  3. react 组件引用组件_动画化React组件

    react 组件引用组件 So, you want to take your React components to the next level? Implementing animations c ...

  4. react前端项目_如何使用React前端设置Ruby on Rails项目

    react前端项目 The author selected the Electronic Frontier Foundation to receive a donation as part of th ...

  5. react前端开发_是的,React正在接管前端开发。 问题是为什么。

    react前端开发 by Samer Buna 通过Samer Buna 是的,React正在接管前端开发. 问题是为什么. (Yes, React is taking over front-end ...

  6. react java编程_快速上手React编程 PDF 下载

    资料目录: 第1章  初积React  3 1.1  什么是React  4 1.2 React解决的问题  5 1.3  使用React的好处  6 1.3.1  简单性  6 1.3.2  速度和 ...

  7. vue 筛选组件_记一个复杂组件(Filter)的从设计到开发

    此文前端框架使用 rax,全篇代码暂未开源(待开源) 原文链接地址:Nealyang/PersonalBlog 前言 貌似在面试中,你如果设计一个 react/vue 组件,貌似已经是司空见惯的问题了 ...

  8. 创建react应用程序_如何使用React创建一个三层应用程序

    创建react应用程序 Discover Functional JavaScript was named one of the best new Functional Programming book ...

  9. react实战课程_在使用React一年后,我学到的最重要的课程

    react实战课程 by Tomas Eglinskas 由Tomas Eglinskas 在使用React一年后,我学到的最重要的课程 (The most important lessons I'v ...

最新文章

  1. ZooKeeper典型应用场景一览
  2. mysql+提升更新语句效率_MySQL加快批量更新 UPDATE优化
  3. [BUUCTF-pwn]——[BJDCTF 2nd]secret
  4. Centos下mysql数据库备份与恢复的方法
  5. Go开发报错 -- Golang strings.Builder type undefined
  6. 这次,让算法走下神坛!
  7. 当图变成了一棵树(纠结的生成树)
  8. 使用python3编写冒险岛079登录器
  9. 3步接入顺丰快递云打印电子面单接口API
  10. CNC加工老师傅的经验分享 这些你都知道吗?
  11. python爬取B站弹幕
  12. 证件照怎么制作?超简单的证件照制作教程来了
  13. 叫谁修猫呢?叫蓝总|ONES 人物
  14. [css选择器] 后代选择器
  15. 解决URP资源的材质成洋红色问题
  16. java存储张三李四_JAVA_day14_面向对象的特征
  17. Java网课①--->期末考试试卷
  18. 医院计算机培训ppt,计算机培训讲义.ppt
  19. Exception获取getMessage()为空
  20. Yolov5代码详解——detect.py

热门文章

  1. safari无法打开网页是什么原因?mac上的Safari浏览器打不开网页怎么办?
  2. 用c语言绘制简单的平面图形
  3. python 使用session_Python(Django之Session详解及使用)
  4. 2021年化工自动化控制仪表考试题及化工自动化控制仪表考试总结
  5. Win8重装IE浏览器的方法
  6. 煽情的儿子543=随笔
  7. 10.PS-铅笔工具
  8. SQL:查询每科目前三名学生及分数
  9. python中的二维数组_Python中的二维数组实例(list与numpy.array)
  10. 电脑无法开机:问题描述1、显示器不亮,鼠标键盘正常。2、显示器不亮,鼠标键盘也不亮。3、主机灯闪烁,风扇转转停停。4、风扇不转,主机没灯,电源损坏。