构建完整的前端功能, 无须等待后端 API 就绪。

Mirage JS 是一个 API 模拟库,它可以帮你构建、 测试并演示完整可运行的 JavaScript 应用程序, 而不必依赖任何后端服务。

前端开发的 最佳 DX

告别后端环境配置的痛苦, 只须对 UI 做些 hack。Mirage 与你的其余前端代码 一起运行,因此无需学习新的 基础结构。

新成员可以克隆你的前端代码库,运行 npm install, 并在数秒内启动并运行一个完整的 本地脱机开发环境 -- 无需环境 变量或 auth 令牌。

import React from "react"
import ReactDOM from "react-dom"
import App from "./App"
import { Server } from "miragejs"

new Server({
  routes() {
    this.namespace = 'api'

    this.get('/todos', ({ db }) => {
      return db.todos;
    }))
  }
})

ReactDOM.render(
  <App />,
  document.getElementById("root")
)

We’ve been using Mirage at Heroku since 2015 on critical customer-facing apps. It's enabled our team to grow without sacrificing speed in either our development or testing workflows.

For me, the real magic of Mirage is that it lets us write tests from the user's perspective. We take user stories from our product team and translate them 1:1 into tests, without ever having to break flow by stepping outside the front-end toolchain.

Mirage is, in short, an essential tool for every UI developer.

Jamie White

Software Engineer at Salesforce/Heroku

Write high-level UI tests that stress your networking code.

利用 Mirage,无论处于什么开发阶段, 你都可以针对自己的 API 编写自动化测试。测试你的应用程序如何 处理 0 篇、10 篇、1000 篇博文,甚至测试 当服务器运行缓慢或出现错误时的行为。

测试用例中没有混乱的模拟代码或手工编写的 API 响应。 只是真实场景验证 整个应用程序的全部功能。

Browsers
it("shows a message if there are no todos", async () => {
  const { getByTestId } = render(<App />)
  await waitForElementToBeRemoved(() => getByTestId("loading"))

  expect(getByTestId("no-todos")).toBeInTheDocument()
})

it("shows existing todos", async () => {
  server.createList("todo", 3)

  const { getByTestId, getAllByTestId } = render(<App />)
  await waitForElementToBeRemoved(() => getByTestId("loading"))

  expect(getAllByTestId("todo")).toHaveLength(3)
})

it("can complete a todo", async () => {
  server.create("todo", { text: "Todo 1", isDone: false })
  server.create("todo", { text: "Todo 2", isDone: false })

  const { getByTestId, getAllByTestId } = render(<App />)
  await waitForElementToBeRemoved(() => getByTestId("loading"))
  const todos = getAllByTestId("todo")
  userEvent.click(todos[1].querySelector("input[type='checkbox']"))
  await waitForElementToBeRemoved(() => getByTestId("saving"))

  expect(todos[0].querySelector('input[type="checkbox"]').checked).toBe(false)
  expect(todos[1].querySelector('input[type="checkbox"]').checked).toBe(true)
  expect(server.db.todos[1].isDone).toBe(true)
})

it("can create a todo", async () => {
  const { getByTestId } = render(<App />)
  await waitForElementToBeRemoved(() => getByTestId("loading"))

  const newTodoForm = await waitForElement(() => getByTestId("new-todo-form"))
  userEvent.type(newTodoForm.querySelector("input"), "Walk the dog")
  fireEvent.submit(getByTestId("new-todo-form"))
  await waitForElementToBeRemoved(() => getByTestId("saving"))

  const todo = getByTestId("todo")
  expect(todo.querySelector('input[type="checkbox"]').checked).toBe(false)
  expect(todo.querySelector('input[type="text"]').value).toBe("Walk the dog")
  expect(server.db.todos.length).toBe(1)
  expect(server.db.todos[0].text).toBe("Walk the dog")
})

it("shows a message if there are no todos", async () => {
  const { getByTestId } = render(<App />)
  await waitForElementToBeRemoved(() => getByTestId("loading"))

  expect(getByTestId("no-todos")).toBeInTheDocument()
})

it("shows existing todos", async () => {
  server.createList("todo", 3)

  const { getByTestId, getAllByTestId } = render(<App />)
  await waitForElementToBeRemoved(() => getByTestId("loading"))

  expect(getAllByTestId("todo")).toHaveLength(3)
})

it("can complete a todo", async () => {
  server.create("todo", { text: "Todo 1", isDone: false })
  server.create("todo", { text: "Todo 2", isDone: false })

  const { getByTestId, getAllByTestId } = render(<App />)
  await waitForElementToBeRemoved(() => getByTestId("loading"))
  const todos = getAllByTestId("todo")
  userEvent.click(todos[1].querySelector("input[type='checkbox']"))
  await waitForElementToBeRemoved(() => getByTestId("saving"))

  expect(todos[0].querySelector('input[type="checkbox"]').checked).toBe(false)
  expect(todos[1].querySelector('input[type="checkbox"]').checked).toBe(true)
  expect(server.db.todos[1].isDone).toBe(true)
})

it("can create a todo", async () => {
  const { getByTestId } = render(<App />)
  await waitForElementToBeRemoved(() => getByTestId("loading"))

  const newTodoForm = await waitForElement(() => getByTestId("new-todo-form"))
  userEvent.type(newTodoForm.querySelector("input"), "Walk the dog")
  fireEvent.submit(getByTestId("new-todo-form"))
  await waitForElementToBeRemoved(() => getByTestId("saving"))

  const todo = getByTestId("todo")
  expect(todo.querySelector('input[type="checkbox"]').checked).toBe(false)
  expect(todo.querySelector('input[type="text"]').value).toBe("Walk the dog")
  expect(server.db.todos.length).toBe(1)
  expect(server.db.todos[0].text).toBe("Walk the dog")
})

共享同一个 完整的用户界面, 而无需运行后端服务。

由于 Mirage 实际上模拟了整个 API 服务器, 因此你可以演示一个可点击的、可运行的 JavaScript 应用程序原型,而无需运行任何 后端服务。

在你开始购买昂贵的服务器基础设施之前, 从用户那里获得高质量的反馈并比以往更快地进行迭代。

Honestly, I can't recommend this tool enough. Finally, an idiomatic way for frontend developers to prototype and test an entire feature without touching a real API! Productivity just goes through the roof.

Roman Sandler

Developer at 500tech

开始使用 Mirage

Mirage 可与所有主流的 JavaScript 框架,库和测试工具一起使用。