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-library , ng-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上關注我,以獲取更多精彩文章。 您可能想看看我以前的文章:
Do you want a better understanding of Buffer in Node.js? Check this out
您是否希望更好地了解Node.js中的Buffer? 看一下這個
Functional setState is the future of React
函數setState是React的未來
翻譯自: https://www.freecodecamp.org/news/testing-socket-io-client-app-using-jest-and-react-testing-library-9cae93c070a3/
react jest測試