原文連結:Testing React app with Cypress
兩週(原文釋出於2017/11/6)以前,Cypress 開源了並且適用於任何人。
Cypress 是一個工具,它使得你的端對端測試寫起來更快。
對瀏覽器中執行的任何內容進行快速,簡單和可靠的測試。
讓我們來試一試,並驗證這是真的!
我們將把 Cypress 與我們的專案之一--Eedi
整合在一起。 Eedi
是英國教師、學生及家長的絕佳教育平臺。關鍵是,任何使用它的人,在瀏覽時都有愉快而流暢的體驗,並且所有的功能都能按預期工作。
配置
在我們應用程式的根目錄下,讓我們新增 Cypress 作為 dev 依賴。
$ yarn add --dev cypress
複製程式碼
調整 package.json
中的 "scripts":
"scripts": {
...
"cypress:open": "cypress open"
}
複製程式碼
就像正常開發一樣,在本地執行服務,然後在新的終端視窗中開啟 Cypress:
$ yarn run cypress:open
複製程式碼
過了一會兒,Cypress 應該開啟了,我們應該看到一個視窗彈出。
在這裡,我們可以訪問我們所有的測試,甚至開箱即用。
Cypress 已建立新的資料夾cypress
與子資料夾 fixtures
, integration
和support
。它還新增了一個空配置檔案cypress.json
。
由於我們經常訪問我們的根路徑,因此將它抽象為配置檔案是一種很好的做法。開啟cypress.json
檔案,並新增一個帶有鍵baseUrl
和 url 的新條目作為值:
{
"baseUrl": "http://localhost:3000"
}
複製程式碼
在example_spec.js
檔案中,我們可以看到'Kitchen Sink Tests',當我們想要瀏覽一些常見的測試場景時可以派上用場。但是讓我們現在寫我們自己的測試。
測試登入
登入是任何應用程式最重要的功能之一。如果做得不好,使用者將無法看到我們其它的工作,並且再做其他事情就沒有任何意義。
建立一個新檔案login_spec.js
。在這裡,我們將測試我們關於登入的所有邏輯。
讓我們寫下我們的第一個測試,讓我們來檢查一下 happy path 是否如預期一樣工作:
describe('Log In', () => {
it('succesfully performs login action', () => {
// 訪問 'baseUrl'
cy.visit('/');
// 斷言我們是否處於好的位置 - 搜尋'smarter world'
cy.contains('smarter world');
// 搜尋帶有 'Teachers' 的div, 並點選它
cy.get('a[data-testid="main-link-teachers"]').click();
// 檢查url是否改變
cy.url().should('includes', 'teachers');
cy.contains('more time to teach');
// 找到Login按鈕並點選它
cy.get('button[data-testid="menu-button-login"]').click();
// 檢查url是否改變
cy.url().should('includes', '/login');
// 提交輸入表單並點選提交按鈕
cy.get('input[data-testid="login-form-username"]').type('test@email.com');
cy.get('input[data-testid="login-form-password"]').type('password');
cy.get('button[data-testid="login-form-submit"]').click();
// 驗證是否被重定向
cy.url({ timeout: 3000 }).should('includes', '/c/');
});
});
複製程式碼
現在,請轉到 Cypress 應用程式並選擇我們剛剛建立的測試。它應該在一個檔案中執行所有的測試,我們可以看到它們的表現如何:
在測試執行器的左側窗格中,我們可以看到 Cypress 執行的所有操作,查詢到的元素以及瀏覽器重定向的元素。我們還可以使用漂亮的時間旅行功能,並檢查我們測試的每一步。讓我們停下來!修改測試的第12行:
cy.contains('Log In').click()
複製程式碼
它失敗了。這很好,我們已經確定 happy path 確實很 happy。Cypress 為我們提供了詳細的堆疊跟蹤 -- 發生了什麼問題以及在哪裡發生的問題。
新增更多的用例:
- 不成功的登入操作應該會產生錯誤訊息
- 未經授權的使用者應該無法訪問受限制的網址
describe('Log In', () => {
it('succesfully performs login action', () => {
...
});
it('displays error message when login fails', () => {
// 直接轉到登入路徑
cy.visit('/login');
// 嘗試使用不正確的憑證登入
cy.get('input[data-testid="login-form-username"]').type('test@email.com');
cy.get('input[data-testid="login-form-password"]').type('fail_password');
cy.get('button[data-testid="login-form-submit"]').click();
// 應該出現錯誤資訊
cy.contains('Something went wrong');
});
it('redirects unauthorized users', () => {
// 轉到受保護的路徑
cy.visit('/c');
// 應該重定向到登入頁面
cy.url().should('contains', '/login');
});
});
複製程式碼
我們儲存測試檔案之後,Cypress應該重新執行所有的測試:
測試登出
下一個要覆蓋的功能是登出操作。我們希望確定該使用者可以正確地從我們的應用程式登出。聽起來很簡單,對吧?
但是,讓我們再考慮一下...為了登出,我們需要先登入,對吧?我們是否應該重用先前測試的程式碼,然後再新增更多邏輯?聽起來很傻,我們是開發者,我們可以做得更好!
Cypress 提供了另一個便利的功能 -- 命令。它允許我們建立可以在任何測試中重用的自定義操作。而且由於大多數場景應該為登入使用者編寫,因此此操作是自定義命令的完美候選。
開啟位於support
資料夾中的commands.js
檔案。 Cypress 為我們提供了一些示例,取消註釋即可使用!
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
複製程式碼
使用我們的自定義行為來增強此登入命令,但首先讓我們考慮一下我們想要做什麼。
我們已經測試了登入,不是嗎?所以,我們接下來要寫的每一個測試都是重複相同的步驟,是沒有意義的。我們甚至可以閱讀文件:
完全測試登入流程 - 但只有一次!
同樣的:
在每次測試之前,請勿使用您的使用者介面登入。
那我們能做什麼呢?
我們可以使用cy.request()
直接向我們的後端服務請求登入,然後像往常一樣繼續。如下:
Cypress.Commands.add('login', (email, password) => {
// 向後端發出POST請求
// 我們正在使用GraphQL,因此我們正在通過轉變:
cy
.request({
url: 'http://localhost:4000/graphql',
method: 'POST',
body: {
query:
'mutation login($email: String!, $password: String!) {loginUser(email: $email, password: $password)}',
variables: { email, password },
},
})
.then(resp => {
// 斷言來自伺服器的響應
expect(resp.status).to.eq(200);
expect(resp.body).to.have.property('data');
// 我們所有的private路徑都會檢查存在redux store上的auth token,所以讓我們把它傳遞到那裡
window.localStorage.setItem(
'reduxPersist:user',
JSON.stringify({ refreshToken: resp.body.data.loginUser })
);
// 到儀表盤
cy.visit('/c');
});
});
複製程式碼
現在,在每個測試中,我們可以呼叫cy.login('username','password')
,並且它應該執行登入操作而不需要使用UI。
現在我們準備測試登出操作,建立logout_spec.js
並新增一些斷言:
const baseUrlMatcher = new RegExp('localhost:3000/$');
describe('Log out user properly', () => {
// 在每次測試前登入:
beforeEach(() => {
cy.login('test@email.com', 'password');
});
it('can select dropdown and perform logout action', () => {
// 檢查我們是否登入:
cy.url().should('contains', '/c/');
cy.get('div[data-testid="main-menu-settings"]').click();
cy
.get('.Popover-body ul li')
.first()
.click();
cy.url().should('match', baseUrlMatcher);
});
it('/logout url should work as well', () => {
cy.url().should('contains', '/c/');
cy.visit('/log-out');
cy.url().should('match', baseUrlMatcher);
});
it('should clear auth token from local storage', () => {
cy.url().should('contains', '/c/');
cy.visit('/logout');
cy.url().should('match', baseUrlMatcher);
const user = JSON.parse(window.localStorage.getItem('reduxPersist:user'));
assert.isUndefined(user.token, 'refreshToken is undefined');
});
});
複製程式碼
觀察它們失敗:
然後修改第14行和第20行(將first()
更改為last()
,將cy.visit('log-out')
更改為cy.visit('logout')
並觀察測試如何通過:
TL;DR
總之,用 Cypress 寫測試真的很有趣。
正如所宣稱的,配置幾乎為零,編寫斷言很簡單,感覺很自然,而且 GUI 非常棒!您可以進行時間旅行,除錯所有步驟,並且因為它們都作為 Electron 應用程式啟動,所以我們甚至可以訪問開發者工具以瞭解每個動作發生了什麼。
網路已經進化,測試依舊會如此。
讓我們寫一些測試吧,願原力與你同在!
關注微信公眾號:創宇前端(KnownsecFED),碼上獲取更多優質乾貨!