react jest测试_如何使用Jest和react-testing-library测试Socket.io-client应用程序

白光耀
2023-12-01

react jest测试

by Justice Mba

由Mba法官

如何使用Jest和react-testing-library测试Socket.io-client应用程序 (How to test a Socket.io-client app using Jest and the react-testing-library)

Testing the quality of real-time Socket.io-client integration seems to have sunk into oblivion, maybe because the UIs had a long history of testability issues. Let’s fix this!

测试实时Socket.io-客户端集成的质量似乎已被遗忘,这可能是因为UI拥有悠久的可测试性问题历史。 让我们解决这个问题!

Quickly google “testing socket.io app”.

快速谷歌“测试socket.io应用程序”。

The first two result pages (just don’t bother opening the rest of the pages) are all examples and tutorials focusing on testing the server-side socket.io integration. No one is talking about the quality of socket.io-client integration on the front-end, how the User Interface will look when it receives certain events, and if the front-end code is actually emitting the right events.

前两个结果页面(不必费心打开其余页面)都是专注于测试服务器端socket.io集成的示例和教程。 没有人会谈论前端上的socket.io-client集成的质量,用户界面在接收某些事件时的外观以及前端代码是否实际上发出了正确的事件。

But why? Does this just mean that people don’t really care about the quality of their real-time apps on the front-end — the meat of the software? I don’t think so. My guess is: Testing UIs was just too hard!

但为什么? 这是否仅表示人们并不真正在乎前端实时应用程序的质量-软件的实质? 我不这么认为。 我的猜测是:测试用户界面实在是太辛苦了!

User Interfaces have had a long history of testability issues. UIs are never stable. The testing tools we have had available to us easily lead to writing very brittle UI tests. Thus, people tend to focus their time and energy on testing their socket.io apps only on the server-side.

用户界面具有悠久的可测试性问题历史。 用户界面永远不会稳定。 我们已经拥有的测试工具很容易导致编写非常脆弱的UI测试。 因此,人们倾向于将时间和精力集中在仅在服务器端测试其socket.io应用程序上。

But that doesn’t feel right. It is only the UI that makes our user confident that they’re actually accomplishing the purpose of using our app. But then, unto us a UI testing tool has been born!

但这感觉不对。 只有用户界面才能使我们的用户确信他们实际上已经完成了使用我们的应用程序的目的。 但是,到了我们,UI测试工具诞生了!

React测试库 (react-testing-library)

It was a few months ago that my friend and mentor Kent C. Dodds released this beautiful tool for testing react apps. Ever since then, I no longer just love the idea of testing UIs, but actually love testing them. I have literally dug out and tested all the UI code I gave up on testing because of its complexity :).

几个月前,我的朋友和导师Kent C. Dodds 发布了这个漂亮的工具来测试React应用。 从那时起,我不再只是喜欢测试UI的想法 ,而是实际上喜欢测试它们。 实际上,我已经挖出并测试了由于复杂性而放弃的所有UI代码:)。

In my experience-based opinion, the react-testing-library is the panacea for all UI test issues. It is not just a testing tool, it is a testing approach.

以我的经验为基础,React测试库是解决所有UI测试问题的灵丹妙药。 它不仅是一种测试工具,还是一种测试方法。

Note: If your’re not a React person, there is vue-testing-library, ng-testing-library and others, all built on top of the dom-testing-library.

注意:如果您不是React人士,则可以在dom-testing-library之上构建vue-testing-libraryng-testing-library

The best feature of the react-testing-library is probably its support of UI TDD. According to the docs, it’s primary guiding principle is:

react-testing-library的最佳功能可能是它对UI TDD的支持。 根据文档,它的主要指导原则是:

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

测试越类似于软件使用方式,就越能给您信心。

This is the “approach” I’m talking about. Test your UIs just as your non-techie friend would. Your user probably neither knows nor cares what your code looks like. And nor should your test. That gives us the power to use TDD on our UIs.

这就是我正在谈论的“方法”。 就像您的非技术朋友一样测试您的UI。 您的用户可能既不知道也不在乎您的代码是什么样。 而且也不应该进行测试。 这使我们能够在用户界面上使用TDD。

This is how we’re going to write our socket.io-client test — test everything without thinking about the code. Now let’s do it!

这就是我们编写socket.io-client测试的方式—测试所有内容而无需考虑代码。 现在开始吧!

测试Telegram应用 (Testing out the Telegram app)

From our very talented Telegram UI designer, bellow are the designs of the Telegram app we’ll be testing.

来自我们非常有才华的Telegram UI设计师,下面是我们将要测试的Telegram应用程序的设计。

Looking at the design, I see a couple of real-time features our user would want to make sure the app performs, otherwise they’ll close the tab. Here are some of them:

看一下设计,我看到了我们的用户想要确保应用程序执行的几个实时功能,否则他们将关闭选项卡。 这里是其中的一些:

  • App should get messages

    应用程序应该收到消息
  • App should tell when/if a message is sent or not

    应用应告知何时/是否发送消息
  • App should tell when/if a message is delivered or not

    应用应告知何时/是否传递消息
  • App should tell when a friend comes online/goes offline

    应用应该告诉朋友何时在线/离线
  • App should tell when a friend is typing

    应用应该告诉朋友何时输入

Okay, the list goes on…but let’s work on these first.

好的,清单还在继续……但是让我们先处理这些。

接收讯息 (Receiving messages)

Let’s look at how a user would know if they received a message as an example. First, create a test file, then import the chat.js file and its mocked dependencies. If you’re new to mocking or stuff like that, then Kent C. Dodds should really be your friend. He’s got everything covered on JavaScript testing, so just follow him on here, Twitter, and everywhere else.

让我们以用户为例,看看用户如何知道他们是否收到了一条消息。 首先,创建一个测试文件,然后导入chat.js文件及其模拟的依赖项。 如果您不喜欢嘲笑或类似的东西,那么Kent C. Dodds应该真的是您的朋友。 他已经涵盖了JavaScript测试的所有内容,因此只需在这里,Twitter和其他任何地方关注他即可。

Now as I was writing this line, I was thinking he should just write a book on JS testing so I tweeted:

现在,当我写这行代码时,我想他应该只写一本关于JS测试的书,所以我发了一条推文:

And hopefully, he will eventually :)

希望他最终会:)

Back to our test file:

返回我们的测试文件:

// chat.test.jsimport React from 'react';import io from 'socket.io-client';
import Chat from './chat';

Because, we’re only doing integration testing here, we don’t really want to emit socket.io events to the server. So we need to mock out socket.io-client. For more information on mocking, see Kent’s article “But really, what is a JavaScript mock?” as well as this section from the Jest docs on Jest’s Mock Functions.

因为,我们只在这里进行集成测试,所以我们实际上并不想向服务器发出socket.io事件。 因此,我们需要模拟出socket.io-client。 有关模拟的更多信息,请参见Kent的文章“ 但是,实际上,什么是JavaScript模拟? ”以及Jest文档中有关Jest的Mock Functions的这一部分。

Once you understand how to mock, the next thing is understanding what your module is doing, and then fake the implementation.

理解了模拟之后,接下来的事情就是了解您的模块在做什么,然后伪造实现。

// socket.io-client.js
let EVENTS = {};
function emit(event, ...args) { EVENTS[event].forEach(func => func(...args));}
const socket = { on(event, func) {  if (EVENTS[event]) {   return EVENTS[event].push(func);  }  EVENTS[event] = [func]; }, emit};
export const io = { connect() {  return socket; }};
// Additional helpers, not included in the real socket.io-client,just for out test.
// to emulate server emit.export const serverSocket = { emit }; // cleanup helperexport function cleanup() { EVENTS = {}}
export default io;

With that, we have a good-enough socket.io-client mock for our test. Let’s use it.

这样,我们就可以完成一个足够好的socket.io-client模拟测试。 让我们使用它。

// chat.test.jsimport React from 'react';import mockio, {serverSocket, cleanUp } from 'socket.io-client';
import Chat from './chat';

Now let’s write our first test. The traditional TDD approach says we’ll write a test for a feature, see it fail, then go implement the feature to satisfy our test. For brevity, we’re not going to do exactly that, as this article focuses on testing.

现在让我们编写第一个测试。 传统的TDD方法说,我们将为某个功能编写一个测试,看到它失败,然后实施该功能来满足我们的测试。 为简便起见,我们将不做确切的事情,因为本文重点关注测试。

Following the react-testing-library approach, the first thing you do before you write any test is to ask yourself: “How will a user test this feature?” For the first test in our list above, you ask yourself, “how will a user know that they’re getting the messages their friend is sending?”. To test it, they’ll probably tell the person next to them to send them a message.

遵循React测试库的方法,在编写任何测试之前,您要做的第一件事就是问自己:“用户将如何测试此功能?” 对于上面列表中的第一个测试,您问自己:“用户如何知道他们正在收到朋友发送的消息?”。 为了进行测试,他们可能会告诉旁边的人向他们发送消息。

Usually, how that will work is that the user’s friend sends a message to the server, with the user’s address, then the server emits the message to the user. Now, since we’re not testing if the user can send a message at this time, but whether the user can receive a message, let’s havesocket.io server directly send the user a message.

通常,如何工作是用户的朋友将一条带有用户地址的消息发送到服务器,然后服务器将消息发送给用户。 现在,由于我们现在不测试用户是否可以发送消息,而是测试用户是否可以接收消息,因此让socket.io server直接向用户发送消息。

// chat.test.jsimport React from 'react';import mock-io, {serverSocket, cleanUp } from 'socket.io-client';import {render} from 'react-testing-library';
import Chat from './chat';
test('App should get messages', () => {  // first render the app  const utils = render(<Chat />)    // then send a message  serverSocket.emit('message', 'Hey Wizy!');})

Above we imported the render method from the react-testing-library, which is just a wrapper around ReactDom.render. In our text, we use it to render our Chat app. The render method returns a test utility object that contains query methods we can use to query the container of our app — the DOM node render rendered our app into — for DOM nodes our test is interested in. Next in the text, use our mock socket.io server to send a message to the user.

在上面,我们从react-testing-library导入了render方法,该方法只是ReactDom.render的包装。 在我们的文本中,我们使用它来呈现“聊天”应用程序。 render方法返回一个测试实用程序对象,该对象包含查询方法,可用于查询应用程序的container ( render渲染了应用程序的DOM节点render到我们的应用程序的container ,以获取测试感兴趣的DOM节点。在文本中,接下来使用模拟套接字.io服务器向用户发送消息。

Now that we’ve sent a message to the user, think again: how will the user know they’ve gotten the message? From the design above, they’ll definitely have to look at the screen to see the message appear. So to test that, we have to query the container of our app to see if it has any node that contains the message we sent, ‘Hey Wizy!’ To do that, the utility object returned from render has a query method called getByText, so we could simply do:

现在,我们已经向用户发送了一条消息,再想一想:用户如何知道他们已经收到了消息? 从上面的设计中,他们绝对必须看屏幕才能看到消息的出现。 因此,要进行测试,我们必须查询应用程序的容器,以查看它是否具有包含我们发送的消息“嘿,Wizy!”的任何节点。 为此,从render返回的实用程序对象具有一个名为getByText的查询方法,因此我们可以简单地执行以下操作:

expect(utils.getByText('Hey Wizy!')).toBeTruthy();

expect(utils.getByText('Hey Wizy!')).toBeTruthy();

While that might work, unfortunately, we can’t do that. Here’s why: All query methods returned from render will search the entire container for the specified query. That means that getByText, as used above, will search the entire container for the text ‘Hey Wizy!’, then returns the first node that has that text.

不幸的是,尽管这可能起作用,但我们无法做到这一点。 原因如下:从render返回的所有查询方法都将在整个容器中搜索指定的查询。 这意味着,如上面所使用的, getByText将在整个容器中搜索文本“ Hey Wizy!”,然后返回包含该文本的第一个节点。

But that’s not how our user will look for the text. Instead, our user will only look within the ‘messages-section’, the section that contains all the messages. Only if messages appear in that section will they know they’ve got a message. So to make sure our test resembles how the user is using our app, we’ll need to search for the text ‘Hey Wizy!’ only within the messages-section, just as the user would do.

但这不是我们的用户寻找文本的方式。 相反,我们的用户只会 “消息部分”(包含所有消息的部分) 查看。 只有在该部分中出现消息时,他们才知道有消息。 因此,为确保我们的测试与用户使用我们的应用的方式相似,我们需要搜索文本“ Hey Wizy!”。 就像用户一样, 仅在邮件部分中。

For that, the react-testing-library provides us with a unique query method call, within, which helps us focus our query within a particular section of the rendered document. Let’s use it!

为此,该React测试库为我们提供了一个唯一的查询方法调用, within ,这有助于我们集中呈现的文档的特定部分我们的查询。 让我们使用它!

Note: within is a new API that was inspired by this article, so make sure you have the very latest version of the react-testing-library.

注意: within是受本文启发的新API,因此请确保您具有最新版本的react-testing-library。

// chat.test.jsimport React from 'react';import mock-io, {serverSocket, cleanUp } from 'socket.io-client';import {render, within} from 'react-testing-library';
import Chat from './chat';
test('App should get messages', () => {  // first render the app  const utils = render(<Chat />)    // then send a message  serverSocket.emit('message', 'Hey Wizy!');    // the message must appear in the message-section  const messageSection = utils.getByTestId('message-section');  // check withing messageSection to find the received message  const message = within(messageSection).getByText('Hey Wizy!');})

First, we grabbed the message section with a query method getByTestId. To use getByTestId in your test, you have to hard-code it in the DOM. Like so:

首先,我们使用查询方法getByTestId消息部分。 要在测试中使用getByTestId ,必须在DOM中对其进行硬编码。 像这样:

<div data-testid=”message-section” />

<div data-testid=”message-section” />

Because getByTestId does not closely resemble how users locate sections of your app, you should use it only on spacial cases and only when you’re certain there is no better alternative.

由于getByTestId与用户如何定位应用程序的各个部分并不十分相似,因此只应在特殊情况下并且仅在确定没有更好的选择时才使用它。

Still, our test is not relying on the DOM structure. Even if someone changes the div to a section tag or wraps it 10 levels deep in the DOM, our test doesn’t just care about the code — it just cares about the test-id.

尽管如此,我们的测试仍不依赖DOM结构。 即使有人将div更改为section标记或将其包装在DOM中的10个级别中,我们的测试也不只是在乎代码-而是在乎test-id。

Lastly, we use the within method as described earlier to get the received message. If the text is not found, getByText will throw and fail our test.

最后,我们使用前面所述的within方法来获取接收到的消息。 如果找不到该文本,则getByText将抛出并通过我们的测试。

And that’s how we assert that the App can get messages.

这就是我们断言该应用程序可以获取消息的方式。

编写更多测试 (Writing more tests)

Let’s see some more query methods that the react-test-library gives us. We’ll see how we can further combine the APIs we’ve already learned to perform more complex queries without relying on the UI code.

让我们看看react-test-library提供给我们的更多查询方法。 我们将看到如何在不依赖UI代码的情况下进一步组合已经学习的API来执行更复杂的查询。

So now, let’s write the second test: the App should tell the user when/if a message has been sent or not. Also, I think this test is basically doing the same thing as the next one in the list, so let’s merge both into one example.

现在,让我们编写第二个测试:该应用程序应该告诉用户何时/是否发送了一条消息。 另外,我认为该测试基本上与列表中的下一个测试具有相同的功能,因此让我们将两者合并为一个示例。

Again, the first question we ask is…? I know you got it: “how will our user test this feature?” Okay, how you phrase your question might be different, but you get the idea :). So to test the sending message feature, the steps will look like this:

同样,我们要问的第一个问题是……? 我知道您明白了:“我们的用户将如何测试此功能?” 好的,您的问题表达方式可能有所不同,但是您知道了:)。 因此,要测试发送消息功能,步骤将如下所示:

  • The user locates the input to enter their message. Then they enter their message. Finally, they click the send button.

    用户找到输入以输入他们的消息。 然后他们输入他们的信息。 最后,他们单击发送按钮。
  • The message should appear on the message-section

    该消息应出现在消息部分
  • The server will tell if the message got to the server, which means sent

    服务器将告知消息是否到达服务器,即已发送
  • The UI should mark the message as sent

    用户界面应将邮件标记为已发送
  • The server then tells when the message is delivered

    然后,服务器告知邮件何时传递
  • The UI should, in turn, update the message as delivered

    用户界面应依次更新已发送的消息

How does the user locate the input to enter their message? From the UI design we’re working with, they’ve gotta look and find the input with the placeholder ‘message’. (Well, that’s actually the only input on the screen, but even if there are more, the user will identify the input to enter their message by the placeholder or label.)

用户如何定位输入内容以输入其消息? 从我们正在使用的UI设计中,他们必须使用占位符“ message”查找并找到输入。 (好吧,这实际上是屏幕上的唯一输入,但是即使有更多输入,用户也会通过占位符或标签识别输入以输入其消息。)

The react-testing-library has us covered again with a query method called getByPlaceholderText

react-testing-library让我们再次使用了名为getByPlaceholderText的查询方法

// chat.test.jsimport React from 'react';import mock-io, {serverSocket, cleanUp } from 'socket.io-client';import {render, renderIntoDocument, within, cleanup} from 'react-testing-library';
import Chat from './chat';
afterEach(cleanup);
test('App should get messages', () => {  // ...})
test('App should tell when message is sent and delivered', () => {  // first render the app  const utils= renderIntoDocument(<Chat />)    // enter and send a message  utils.getByPlaceholderText('message').value = 'Hello';  utils.getByTestId('send-btn').click()})

So we introduced a couple of new APIs here. The first one is the renderIntoDocument method. We should fire real DOM events, not simulate them, in our test, as that more closely resembles how users use our app.

因此,我们在这里介绍了两个新的API。 第一个是renderIntoDocument方法。 在我们的测试中,我们应该触发真实的DOM事件,而不是模拟它们,因为这与用户使用我们的应用的方式更加相似。

The drawback is that the render method creates and renders our app to an arbitrary DOM node, called container, on the fly. But React handles events via event delegation — attaching a single event for all event types on the document, and then delegating the event to the appropriate DOM node that triggered the event.

缺点是render方法会动态创建应用程序并将其呈现到任意DOM节点(称为container 。 但是React通过事件委托来处理事件-为document所有事件类型附加一个事件,然后将事件委托给触发该事件的相应DOM节点。

So, to fire real DOM events, we need to actually render our app into document.body. That’s what renderIntoDocument does for us.

因此,要触发实际的DOM事件,我们需要将应用实际渲染为document.body 。 这就是renderIntoDocument为我们所做的。

Because we render into the document, we want to always make sure that the document is cleaned up after each test. You guessed right, the cleanup helper function does that for us.

因为我们渲染到文档中,所以我们希望始终确保在每次测试后都清理文档。 您猜对了, 清理助手功能为我们做到了。

In the test, after we enter the value, we click the send button to send our message. If you noticed, looking at the design, there is no send button. But if you pull out your Telegram or WhatsApp right now, you’ll notice that the send button only appears when you’ve actually entered some text in the message input. Our test has just accidentally covered that feature. :)

在测试中,输入值后,单击“发送”按钮发送消息。 如果您注意到设计,则没有发送按钮。 但是,如果您立即拔出Telegram或WhatsApp,您会注意到只有在消息输入中实际输入了一些文本时,发送按钮才会出现。 我们的测试刚刚覆盖了该功能。 :)

Now that we’ve clicked the send button, let’s make some assertions.

现在,单击发送按钮,让我们进行一些断言。

// chat.test.jsimport React from 'react';import mock-io, {serverSocket, cleanUp } from 'socket.io-client';import {render, renderIntoDocument, within, cleanup} from 'react-testing-library';
import Chat from './chat';
afterEach(cleanup);
test('App should get messages', () => {  // ...})
test('App should tell when message is sent/delivered', () => {  // first render the app  const utils = renderIntoDocument(<Chat />)    // enter and send a message  utils.getByPlaceholderText('message').value = 'Hello';  utils.getByTestId('send-btn').click();    // the message should appear on the message section  const messageSection = uitils.getByTestId('message-section');  expect(within(messageSection).getByText('Hello')).toBeTruthy();    // server tells us message is sent  serverSocket.emit('message-sent');
// Now the UI should mark the message as sent  const message = within(messageSection).getByText('Hello');  expect(within(message).getByTestId('sentIcon')).toBeTruthy();
// server tells us it's delivered  serverSocket.emit('message-delivered');
// UI should mark the message as delivered  expect(within(message).getByTestId('deliveredIcon')).toBeTruthy();})

And that’s it. Just exactly as the user would expect, our test expects to see the sent/delivered icon appear next to the message when it’s sent/delivered.

就是这样。 就像用户期望的那样,我们的测试希望在发送/传递消息时看到发送/传递图标出现在消息旁边。

So far, we’ve seen how easy testing a real-time socket.io-client app can be with the react-testing-library. No matter what you are testing, when you follow this approach, you gain more confidence that your app is working as it should. And what is more, we still have zero idea what the implementation of the app will look like. Just as the user, our test just doesn’t care about the implementation!

到目前为止,我们已经看到使用react-testing-library测试实时socket.io-client应用程序是多么容易。 无论您要进行何种测试,采用这种方法时,您都可以确信自己的应用程序可以正常运行。 而且,我们仍然不知道应用程序的实现将是什么样子。 就像用户一样, 我们的测试并不关心实现!

整理起来 (Finishing up)

Lastly, I’ll leave it to you to think about how to write the last two remaining tests on our list:

最后,我让您考虑如何编写清单上剩下的两个测试:

  • App should tell when a friend comes online/goes offline

    应用应该告诉朋友何时在线/离线
  • App should tell when a friend is typing

    应用应该告诉朋友何时输入

Tip: You should have the server socket.io emit the event, then you assert what the UI will look like. Think about how exactly the user will know when a friend is typing, online, offline.

提示:应该让服务器socket.io发出事件,然后声明UI的外观。 考虑一下当朋友在线,离线输入时,用户将如何确切地知道。

If you feel like I’ve done a nice job, and that others deserve a chance to see this, kindly applaud this article to help spread a better approach of testing real-time socket.io-client apps.

如果您觉得我做得不错,而其他人也有机会看到这一点,请对本文表示赞赏,以帮助推广更好的实时套接字.io-client应用程序测试方法。

If you have a question that hasn’t been answered or feel differently about some of the points here, feel free to drop in some comments here or via Twitter.

如果您有未解决的问题或对此处的某些观点有不同的看法,请随时在此处或通过Twitter发表一些评论。

You might also want to follow me here and/or on Twitter for more awesome articles coming up. And you might like to check out my previous articles:

您可能还想在这里和/或在Twitter上关注我,以获取更多精彩文章。 您可能想看看我以前的文章:

翻译自: https://www.freecodecamp.org/news/testing-socket-io-client-app-using-jest-and-react-testing-library-9cae93c070a3/

react jest测试

 类似资料: