使用 Mirage 在 Cypress 中模擬網路請求

使用您的 Mirage 伺服器,透過 Cypress 在不同的伺服器情境下測試您的應用程式。

這是為已在其應用程式中使用 Cypress 的人員提供的快速入門指南。

步驟 1:安裝 Mirage

首先,請確保您已安裝 Mirage

# Using npm
npm install --save-dev miragejs

# Using Yarn
yarn add --dev miragejs

步驟 2:定義您的伺服器

建立一個新的 src/server.js 檔案並定義您的模擬伺服器。

這是一個基本範例

// src/server.js
import { createServer, Model } from "miragejs"

export function makeServer({ environment = "development" } = {}) {
  let server = createServer({
    environment,

    models: {
      user: Model,
    },

    seeds(server) {
      server.create("user", { name: "Bob" })
      server.create("user", { name: "Alice" })
    },

    routes() {
      this.namespace = "api"

      this.get("/users", (schema) => {
        return schema.users.all()
      })
    },
  })

  return server
}

步驟 3:讓 Cypress 為您的應用程式 API 請求定義一個代理函數

將以下程式碼新增至您的 cypress/support/index.js 檔案中

// cypress/support/index.js
Cypress.on("window:before:load", (win) => {
  win.handleFromCypress = function (request) {
    return fetch(request.url, {
      method: request.method,
      headers: request.requestHeaders,
      body: request.requestBody,
    }).then((res) => {
      let content = res.headers.get("content-type").includes("application/json")
        ? res.json()
        : res.text()
      return new Promise((resolve) => {
        content.then((body) => resolve([res.status, res.headers, body]))
      })
    })
  }
})

此程式碼會在您應用程式的 window 物件上定義一個 handleFromCypress 函數。在下一步中,我們將設定您的應用程式,以便在 Cypress 執行時,每當它發出網路請求時都會呼叫此函數。

步驟 4:代理您應用程式的網路請求

在您應用程式的啟動檔案中,當 Cypress 正在執行時,使用 Mirage 將您應用程式的 API 請求代理到 handleFromCypress 函數。

Create React App 使用者,此程式碼會放在 src/index.js

Vue CLI 使用者,此程式碼會放在 src/main.js 中。

import { createServer, Response } from "miragejs"

if (window.Cypress) {
  // If your app makes requests to domains other than / (the current domain), add them
  // here so that they are also proxied from your app to the handleFromCypress function.
  // For example: let otherDomains = ["https://my-backend.herokuapp.com/"]
  let otherDomains = []
  let methods = ["get", "put", "patch", "post", "delete"]

  createServer({
    environment: "test",
    routes() {
      for (const domain of ["/", ...otherDomains]) {
        for (const method of methods) {
          this[method](`${domain}*`, async (schema, request) => {
            let [status, headers, body] = await window.handleFromCypress(
              request
            )
            return new Response(status, headers, body)
          })
        }
      }

      // If your central server has any calls to passthrough(), you'll need to duplicate them here
      // this.passthrough('https://analytics.google.com')
    },
  })
}

現在,每當 Cypress 啟動您的應用程式時,此程式碼會將您應用程式的網路請求委派給我們在上一個步驟中定義的 handleFromCypress 函數。

一旦我們與 Cypress 程式碼一起啟動我們實際配置的 Mirage 伺服器,它將開始攔截來自該函數的請求。

步驟 5:使用您的 Mirage 伺服器撰寫測試

建立一個新的 cypress/integration/app.spec.js 檔案,匯入您的 makeServer 函數,並在每次測試之前和之後啟動和關閉 Mirage。然後,您可以在每個測試中為 Mirage 設定不同的資料情境,並使用該測試來斷言您的 UI 狀態。

import { makeServer } from "../../src/server"

describe("user list", () => {
  let server

  beforeEach(() => {
    server = makeServer({ environment: "test" })
  })

  afterEach(() => {
    server.shutdown()
  })

  it("shows the users from our server", () => {
    server.create("user", { id: 1, name: "Luke" })
    server.create("user", { id: 2, name: "Leia" })

    cy.visit("/")

    cy.get('[data-testid="user-1"]').contains("Luke")
    cy.get('[data-testid="user-2"]').contains("Leia")
  })

  it("shows a message if there are no users", () => {
    // Don't create any users

    cy.visit("/")

    cy.get('[data-testid="no-users"]').should("be.visible")
  })
})

請注意,我們將 environment: test 選項傳遞到我們的 makeServer 函數中,以便 Mirage 不會載入其資料庫種子。這樣,伺服器在每次測試執行時都會從空白開始,並且在我們的測試開始時,我們可以使用 server.create 來設定我們的資料情境。測試環境也會停用記錄和延遲,因此預設情況下,您的 CI 測試記錄會很乾淨,而且您的測試速度會很快。

另請注意我們對 Cypress 的 describe 區塊的使用,因為當我們新增更多檔案時,它們會將我們的 Mirage 伺服器限制在每個 spec 中,防止測試檔案之間出現任何狀態洩漏。

步驟 6:修改您的 Mirage 伺服器以測試不同的伺服器狀態

除了不同的資料情境之外,您還可以利用測試重新設定 Mirage 伺服器以測試新的情境。

例如,您可以像這樣測試錯誤狀態

import { Response } from "miragejs"

it("handles error responses from the server", () => {
  // Override Mirage's route handler for /users, just for this test
  server.get("/users", () => {
    return new Response(500, {}, { error: "The database is on vacation." })
  })

  cy.visit("/")

  cy.get('[data-testid="server-error"]').contains(
    "The database is on vacation."
  )
})

由於 Mirage 與 Cypress 的整合方式,每個測試都會根據您的主要伺服器定義取得一個新的 Mirage 伺服器。您在測試中進行的任何覆寫都會隔離到該測試。