在 Cypress 中使用 Mirage 模拟网络请求

使用 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:代理您的应用程序网络请求

在您的应用程序引导文件中,使用 Mirage 将您的应用程序 API 请求代理到 handleFromCypress 函数(当 Cypress 运行时)。

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")
  })
})

请注意,我们在 makeServer 函数中传递了 environment: test 选项,因此 Mirage 不会加载其数据库种子。这样,服务器在每次测试运行时都是空的,并且在测试开始时,我们可以使用 server.create 设置我们的数据场景。测试环境还禁用了日志记录和延迟,因此默认情况下您的 CI 测试日志将是干净的,您的测试将运行得很快。

还要注意我们对 Cypress 的 describe 块的使用,因为当我们添加更多文件时,它们将使 Mirage 服务器保持在每个规范的范围内,从而防止在测试文件之间出现任何状态泄漏。

步骤 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 服务器。您在测试中进行的任何覆盖将被隔离到该测试。