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

原文連接: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, 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 執行的全部操做,查找到的元素以及瀏覽器重定向的元素。咱們還可使用漂亮的時間旅行功能,並檢查咱們測試的每一步。

讓咱們停下來!修改測試的第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),碼上獲取更多優質乾貨!

相關文章
相關標籤/搜索