【翻譯】基於 Cypress 測試 React 應用

創宇前端發表於2018-03-15

原文連結:Testing React app with Cypress

作者:Adam Trzciński

兩週(原文釋出於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 測試 React 應用
過了一會兒,Cypress 應該開啟了,我們應該看到一個視窗彈出。

在這裡,我們可以訪問我們所有的測試,甚至開箱即用。

Cypress 已建立新的資料夾cypress 與子資料夾 fixtures, integrationsupport。它還新增了一個空配置檔案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 測試 React 應用
在測試執行器的左側窗格中,我們可以看到 Cypress 執行的所有操作,查詢到的元素以及瀏覽器重定向的元素。我們還可以使用漂亮的時間旅行功能,並檢查我們測試的每一步。

讓我們停下來!修改測試的第12行:

cy.contains('Log In').click()
複製程式碼

【翻譯】基於 Cypress 測試 React 應用
它失敗了。這很好,我們已經確定 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 測試 React 應用

測試登出

下一個要覆蓋的功能是登出操作。我們希望確定該使用者可以正確地從我們的應用程式登出。聽起來很簡單,對吧?

但是,讓我們再考慮一下...為了登出,我們需要先登入,對吧?我們是否應該重用先前測試的程式碼,然後再新增更多邏輯?聽起來很傻,我們是開發者,我們可以做得更好!

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');
  });
});
複製程式碼

觀察它們失敗:

【翻譯】基於 Cypress 測試 React 應用
然後修改第14行和第20行(將first()更改為last(),將cy.visit('log-out')更改為cy.visit('logout')並觀察測試如何通過:

【翻譯】基於 Cypress 測試 React 應用

TL;DR

總之,用 Cypress 寫測試真的很有趣。

正如所宣稱的,配置幾乎為零,編寫斷言很簡單,感覺很自然,而且 GUI 非常棒!您可以進行時間旅行,除錯所有步驟,並且因為它們都作為 Electron 應用程式啟動,所以我們甚至可以訪問開發者工具以瞭解每個動作發生了什麼。

網路已經進化,測試依舊會如此。

讓我們寫一些測試吧,願原力與你同在!


關注微信公眾號:創宇前端(KnownsecFED),碼上獲取更多優質乾貨!

【翻譯】基於 Cypress 測試 React 應用

相關文章