原文連接:Testing React app with Cypresshtml
做者:Adam Trzciński前端
兩週(原文發佈於2017/11/6)之前,Cypress 開源了而且適用於任何人。react
Cypress 是一個工具,它使得你的端對端測試寫起來更快。json
對瀏覽器中運行的任何內容進行快速,簡單和可靠的測試。redux
讓咱們來試一試,並驗證這是真的!後端
咱們將把 Cypress 與咱們的項目之一--Eedi
集成在一塊兒。 Eedi
是英國教師、學生及家長的絕佳教育平臺。關鍵是,任何使用它的人,在瀏覽時都有愉快而流暢的體驗,而且全部的功能都能按預期工做。瀏覽器
在咱們應用程序的根目錄下,讓咱們添加 Cypress 做爲 dev 依賴。bash
$ 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')
並觀察測試如何經過:
總之,用 Cypress 寫測試真的頗有趣。
正如所宣稱的,配置幾乎爲零,編寫斷言很簡單,感受很天然,並且 GUI 很是棒!您能夠進行時間旅行,調試全部步驟,而且由於它們都做爲 Electron 應用程序啓動,因此咱們甚至能夠訪問開發者工具以瞭解每一個動做發生了什麼。
網絡已經進化,測試依舊會如此。
讓咱們寫一些測試吧,願原力與你同在!
關注微信公衆號:創宇前端(KnownsecFED),碼上獲取更多優質乾貨!