axios-mock-adapter vs faker vs json-server vs miragejs vs msw vs nock
前端 API 模拟与测试数据生成方案深度对比
axios-mock-adapterfakerjson-servermiragejsmswnock类似的npm包:

前端 API 模拟与测试数据生成方案深度对比

这些库旨在帮助开发者在前端开发过程中模拟后端 API 响应,或生成逼真的测试数据。mswmiragejsaxios-mock-adapternock 专注于拦截网络请求或模拟服务器行为,而 faker 专注于生成随机数据,json-server 则提供一个完整的本地 REST 服务器。它们共同解决了前后端并行开发、单元测试隔离以及数据隐私保护等核心工程问题。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
axios-mock-adapter03,55167.9 kB961 年前MIT
faker0-10.1 MB--MIT
json-server075,67639 kB7072 天前MIT
miragejs05,5312.29 MB2132 年前MIT
msw017,7634.92 MB424 天前MIT
nock013,091185 kB881 个月前MIT

前端 API 模拟与测试数据生成方案深度对比

在现代前端工程化中,依赖真实后端接口进行开发往往效率低下且不稳定。我们需要可靠的工具来模拟 API 响应、生成测试数据,以便前后端并行开发或编写隔离的单元测试。mswmiragejsaxios-mock-adapternockjson-serverfaker 是生态中最常见的解决方案,但它们的实现原理和适用场景截然不同。本文将从拦截机制、环境支持、数据生成能力等维度进行深度剖析。

🕸️ 请求拦截机制:网络层 vs 库层

选择模拟工具时,最关键的区别在于它是在哪个层面拦截请求。是在浏览器网络层、HTTP 客户端层,还是 Node.js 底层。

msw 使用 Service Worker 在浏览器网络层拦截请求。

  • 请求甚至不会离开浏览器,因此你可以捕获所有 fetch 或 XMLHttpRequest。
  • 在 Node.js 中,它通过补丁原生 HTTP 模块实现相同效果。
  • 最接近真实网络行为,无需修改业务代码。
// msw: 定义请求处理器
import { http, HttpResponse } from 'msw'

export const handlers = [
  http.get('/users', () => {
    return HttpResponse.json({ id: 1, name: 'Alice' })
  })
]

axios-mock-adapter 直接挂钩到 axios 实例内部。

  • 仅当代码使用 axios 发起请求时才生效。
  • 如果项目中混用了 fetch 或其他库,这些请求不会被拦截。
  • 配置非常直接,适合纯 axios 项目。
// axios-mock-adapter: 绑定到实例
import AxiosMockAdapter from 'axios-mock-adapter'
import axios from 'axios'

const mock = new AxiosMockAdapter(axios)
mock.onGet('/users').reply(200, { id: 1, name: 'Alice' })

nock 拦截 Node.js 原生的 HTTP/HTTPS 模块。

  • 仅在 Node 环境有效,浏览器中完全无法使用。
  • 能够验证请求是否真正发出,适合测试后端集成。
  • 配置粒度极细,可匹配请求头、请求体。
// nock: 拦截 Node HTTP 请求
import nock from 'nock'

nock('http://api.example.com')
  .get('/users')
  .reply(200, { id: 1, name: 'Alice' })

miragejs 在客户端内部模拟一个服务器。

  • 请求在应用内部被处理,不经过网络栈。
  • 支持定义路由、模型关系和延迟,像真的后端一样。
  • 需要修改应用启动代码来初始化服务器。
// miragejs: 创建客户端服务器
import { createServer } from 'miragejs'

createServer({
  routes() {
    this.get('/users', () => {
      return { id: 1, name: 'Alice' }
    })
  }
})

json-server 运行一个独立的 Node.js 进程作为服务器。

  • 请求真正通过 HTTP 发送到 localhost 端口。
  • 无需在应用代码中引入模拟库,只需改变 API 基础 URL。
  • 适合完全隔离的前后端开发流程。
# json-server: 命令行启动服务
npx json-server --watch db.json --port 3000

🎲 数据生成能力:静态配置 vs 动态生成

模拟接口时,数据是写死的还是动态生成的,决定了测试的覆盖率。

faker 专门用于生成逼真的随机数据。

  • 提供姓名、地址、金融、互联网等多种数据类型。
  • 重要提示:原 faker 包已弃用,新项目请使用 @faker-js/faker
  • 常与其他模拟库配合使用,作为数据源。
// faker: 生成随机数据
import { faker } from '@faker-js/faker' // 注意包名变化

const user = {
  id: faker.string.uuid(),
  name: faker.person.fullName(),
  email: faker.internet.email()
}

msw 支持在处理器中动态返回数据。

  • 可以结合 faker 在每次请求时生成不同数据。
  • 适合测试前端对数据变化的处理能力。
// msw: 动态返回数据
http.get('/users', () => {
  return HttpResponse.json({
    name: faker.person.fullName()
  })
})

miragejs 内置了工厂系统(Factories)来生成数据。

  • 可以定义模型结构,自动填充随机值。
  • 支持创建多条关联数据(如一个用户拥有多篇帖子)。
// miragejs: 工厂生成数据
server.loadFactories({
  user: Factory.define('user', {
    name: faker.person.fullName()
  })
})

json-server 依赖静态 JSON 文件或简单的随机生成器。

  • 默认返回文件中存储的数据。
  • 可以通过插件扩展随机数据生成,但原生支持较弱。
// json-server: db.json 静态数据
{
  "users": [
    { "id": 1, "name": "Alice" }
  ]
}

axios-mock-adapternock 通常返回预设的响应。

  • 需要手动编写逻辑来返回不同数据。
  • 灵活性较低,适合测试特定固定场景。
// nock: 预设响应
nock('http://api.com').get('/users').reply(200, { fixed: true })

🌍 环境支持:浏览器 vs Node.js

运行环境是选择工具时的硬性约束。

package浏览器Node.js备注
msw浏览器用 SW,Node 用补丁
axios-mock-adapter依赖 axios 运行环境
miragejs专为浏览器设计
json-server作为独立进程运行
nock仅限 Node 环境
faker纯工具库,无环境限制

msw 是少数能无缝切换环境的工具。

  • 同一套处理器代码可以在单元测试(Node)和端到端测试(浏览器)中复用。
  • 减少了维护两套模拟逻辑的成本。
// msw: 通用设置
import { setupServer } from 'msw/node' // Node
import { setupWorker } from 'msw/browser' // Browser

nockjson-server 仅限于后端或测试脚本。

  • 无法用于测试浏览器中的实际渲染逻辑。
  • 适合 CI 流水线中的集成测试。
// nock: 仅用于 Node 测试文件
describe('API Test', () => {
  it('fetches data', async () => {
    nock('...').get('/').reply(200)
    // 运行测试
  })
})

miragejs 专注于浏览器开发体验。

  • 可以在开发服务器中直接开启,无需额外进程。
  • 但不适合用于 Node 环境的单元测试。
// miragejs: 通常在 main.js 中引入
if (process.env.NODE_ENV === 'development') {
  require('./mocks')
}

⚙️ 配置复杂度与侵入性

工具对现有代码的侵入程度直接影响迁移成本。

msw 侵入性最低。

  • 不需要修改现有的 fetch 或 axios 调用代码。
  • 只需在入口文件注册 Service Worker。
  • 学习曲线稍陡,需要理解 Service Worker 生命周期。
// msw: 注册 worker
import { worker } from './mocks/browser'
worker.start()

axios-mock-adapter 侵入性中等。

  • 需要访问 axios 实例进行绑定。
  • 如果 axios 实例封装较深,可能需要导出实例供测试使用。
// axios-mock-adapter: 需要实例引用
import axios from './utils/axiosInstance'
const mock = new AxiosMockAdapter(axios)

json-server 零侵入性。

  • 应用代码完全感知不到模拟层的存在。
  • 只需要修改环境变量中的 API 基础 URL。
# 修改环境变量即可切换
API_BASE_URL=http://localhost:3000

miragejs 侵入性较高。

  • 需要初始化服务器实例。
  • 可能需要调整代码以适应其模型定义方式。
// miragejs: 需要初始化逻辑
createServer({
  models: { user: Model },
  routes: { ... }
})

🛑 维护状态与风险提示

在选择长期依赖的库时,维护状态至关重要。

  • faker: 原包已弃用。存在安全漏洞风险且不再更新。必须 迁移至 @faker-js/faker
  • miragejs: 目前处于维护模式。核心功能稳定,但新特性更新缓慢。大型项目需评估长期风险。
  • msw: 活跃维护中。社区增长迅速,是目前前端模拟的事实标准。
  • json-server: 稳定维护。适合原型开发,但功能迭代较慢。
  • nock: 稳定维护。Node 测试领域的经典工具。
  • axios-mock-adapter: 稳定维护。作为专用适配器,功能范围固定。

💡 最终建议

msw 是现代前端项目的首选。

  • 它的网络层拦截机制最真实,且支持浏览器和 Node 双环境。
  • 适合作为团队的标准模拟方案,尤其是配合 React、Vue 等现代框架。

json-server 适合快速原型或前端独立开发阶段。

  • 当后端尚未就绪,且你需要一个完整的 REST 接口来调试 UI 时,它是最快的方案。

nock 是 Node.js 后端测试的标配。

  • 如果你主要编写 Node 服务或脚本,需要验证 HTTP 交互细节,没有比它更好的选择。

faker (及其替代者) 是数据生成的必备工具。

  • 无论选择哪种模拟方案,都建议搭配 @faker-js/faker 来生成动态数据,避免硬编码。

axios-mock-adapter 适合老旧项目或纯 Axios 场景。

  • 如果迁移到 msw 成本过高,且项目只使用 Axios,它可以继续发挥作用。

miragejs 适合需要复杂客户端数据关系的场景。

  • 如果你的应用逻辑高度依赖客户端数据建模,且能接受维护模式的风险,它依然有用。

总结:对于新架构,推荐 msw + @faker-js/faker 组合。对于后端测试,推荐 nock。对于快速原型,推荐 json-server。避免在新项目中使用已弃用的 faker 包。

如何选择: axios-mock-adapter vs faker vs json-server vs miragejs vs msw vs nock

  • axios-mock-adapter:

    如果你的项目完全依赖 axios 进行 HTTP 请求,且只需要在单元测试中快速桩化(stub)特定接口,这是一个轻量级的选择。它直接挂钩到 axios 实例,配置简单,但不适用于其他 HTTP 客户端或网络层测试。

  • faker:

    适用于需要生成大量逼真假数据(如姓名、地址、信用卡号)的场景。但请注意,原 faker 包已停止维护,新项目应直接使用社区维护的 @faker-js/faker 以避免安全风险和缺失的功能。

  • json-server:

    适合需要快速搭建一个真实的 REST API 服务器原型的场景,尤其是前端独立开发时。它通过读取 JSON 文件自动提供 CRUD 接口,但不适合复杂的业务逻辑模拟或高性能测试。

  • miragejs:

    适合需要在浏览器内部模拟完整后端逻辑的应用,支持关系建模和延迟模拟。但该项目目前处于维护模式,更新频率较低,新大型项目需评估长期支持风险。

  • msw:

    现代前端开发的首选方案,通过 Service Worker 在网络层拦截请求,支持浏览器和 Node.js 环境。它不侵入应用代码,能最真实地模拟网络行为,适合集成测试和开发环境。

  • nock:

    专为 Node.js 环境设计,用于拦截底层 HTTP 请求。如果你主要在编写后端服务测试或 Node.js 脚本测试,且需要验证具体的 HTTP 头部和请求体,这是最强大的工具,但不能用于浏览器。

axios-mock-adapter的README

axios-mock-adapter

Axios adapter that allows to easily mock requests

Installation

Using npm:

$ npm install axios-mock-adapter --save-dev

It's also available as a UMD build:

axios-mock-adapter works on Node as well as in a browser, it works with axios v0.17.0 and above.

Example

Mocking a GET request

const axios = require("axios");
const AxiosMockAdapter = require("axios-mock-adapter");

// This sets the mock adapter on the default instance
const mock = new AxiosMockAdapter(axios);

// Mock any GET request to /users
// arguments for reply are (status, data, headers)
mock.onGet("/users").reply(200, {
  users: [{ id: 1, name: "John Smith" }],
});

axios.get("/users").then(function (response) {
  console.log(response.data);
});

Mocking a GET request with specific parameters

const axios = require("axios");
const AxiosMockAdapter = require("axios-mock-adapter");

// This sets the mock adapter on the default instance
const mock = new AxiosMockAdapter(axios);

// Mock GET request to /users when param `searchText` is 'John'
// arguments for reply are (status, data, headers)
mock.onGet("/users", { params: { searchText: "John" } }).reply(200, {
  users: [{ id: 1, name: "John Smith" }],
});

axios
  .get("/users", { params: { searchText: "John" } })
  .then(function (response) {
    console.log(response.data);
  });

When using params, you must match all key/value pairs passed to that option.

To add a delay to responses, specify a delay amount (in milliseconds) when instantiating the adapter

// All requests using this instance will have a 2 seconds delay:
const mock = new AxiosMockAdapter(axiosInstance, { delayResponse: 2000 });

You can restore the original adapter (which will remove the mocking behavior)

mock.restore();

You can also reset the registered mock handlers with resetHandlers

mock.resetHandlers();

You can reset both registered mock handlers and history items with reset

mock.reset();

reset is different from restore in that restore removes the mocking from the axios instance completely, whereas reset only removes all mock handlers that were added with onGet, onPost, etc. but leaves the mocking in place.

Mock a low level network error

// Returns a failed promise with Error('Network Error');
mock.onGet("/users").networkError();

// networkErrorOnce can be used to mock a network error only once
mock.onGet("/users").networkErrorOnce();

Mock a network timeout

// Returns a failed promise with Error with code set to 'ECONNABORTED'
mock.onGet("/users").timeout();

// timeoutOnce can be used to mock a timeout only once
mock.onGet("/users").timeoutOnce();

Passing a function to reply

mock.onGet("/users").reply(function (config) {
  // `config` is the axios config and contains things like the url

  // return an array in the form of [status, data, headers]
  return [
    200,
    {
      users: [{ id: 1, name: "John Smith" }],
    },
  ];
});

Passing a function to reply that returns an axios request, essentially mocking a redirect

mock.onPost("/foo").reply(function (config) {
  return axios.get("/bar");
});

Using a regex

mock.onGet(/\/users\/\d+/).reply(function (config) {
  // the actual id can be grabbed from config.url

  return [200, {}];
});

Using variables in regex

const usersUri = "/users";
const url = new RegExp(`${usersUri}/*`);

mock.onGet(url).reply(200, users);

Specify no path to match by verb alone

// Reject all POST requests with HTTP 500
mock.onPost().reply(500);

Chaining is also supported

mock.onGet("/users").reply(200, users).onGet("/posts").reply(200, posts);

.replyOnce() can be used to let the mock only reply once

mock
  .onGet("/users")
  .replyOnce(200, users) // After the first request to /users, this handler is removed
  .onGet("/users")
  .replyOnce(500); // The second request to /users will have status code 500
// Any following request would return a 404 since there are
// no matching handlers left

Mocking any request to a given url

// mocks GET, POST, ... requests to /foo
mock.onAny("/foo").reply(200);

.onAny can be useful when you want to test for a specific order of requests

// Expected order of requests:
const responses = [
  ["GET", "/foo", 200, { foo: "bar" }],
  ["POST", "/bar", 200],
  ["PUT", "/baz", 200],
];

// Match ALL requests
mock.onAny().reply((config) => {
  const [method, url, ...response] = responses.shift();
  if (config.url === url && config.method.toUpperCase() === method)
    return response;
  // Unexpected request, error out
  return [500, {}];
});

Requests that do not map to a mock handler are rejected with a HTTP 404 response. Since handlers are matched in order, a final onAny() can be used to change the default behaviour

// Mock GET requests to /foo, reject all others with HTTP 500
mock.onGet("/foo").reply(200).onAny().reply(500);

Mocking a request with a specific request body/data

mock.onPut("/product", { id: 4, name: "foo" }).reply(204);

Using an asymmetric matcher, for example Jest matchers

mock
  .onPost(
    "/product",
    { id: 1 },
    {
      headers: expect.objectContaining({
        Authorization: expect.stringMatching(/^Basic /),
      })
    }
  )
  .reply(204);

Using a custom asymmetric matcher (any object that has a asymmetricMatch property)

mock
  .onPost("/product", {
    asymmetricMatch: function (actual) {
      return ["computer", "phone"].includes(actual["type"]);
    },
  })
  .reply(204);

.passThrough() forwards the matched request over network

// Mock POST requests to /api with HTTP 201, but forward
// GET requests to server
mock
  .onPost(/^\/api/)
  .reply(201)
  .onGet(/^\/api/)
  .passThrough();

Recall that the order of handlers is significant

// Mock specific requests, but let unmatched ones through
mock
  .onGet("/foo")
  .reply(200)
  .onPut("/bar", { xyz: "abc" })
  .reply(204)
  .onAny()
  .passThrough();

Note that passThrough requests are not subject to delaying by delayResponse.

If you set onNoMatch option to passthrough all requests would be forwarded over network by default

// Mock all requests to /foo with HTTP 200, but forward
// any others requests to server
const mock = new AxiosMockAdapter(axiosInstance, { onNoMatch: "passthrough" });

mock.onAny("/foo").reply(200);

Using onNoMatch option with throwException to throw an exception when a request is made without match any handler. It's helpful to debug your test mocks.

const mock = new AxiosMockAdapter(axiosInstance, { onNoMatch: "throwException" });

mock.onAny("/foo").reply(200);

axios.get("/unexistent-path");

// Exception message on console:
//
// Could not find mock for: 
// {
//   "method": "get",
//   "url": "http://localhost/unexistent-path"
// }

As of 1.7.0, reply function may return a Promise:

mock.onGet("/product").reply(function (config) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      if (Math.random() > 0.1) {
        resolve([200, { id: 4, name: "foo" }]);
      } else {
        // reject() reason will be passed as-is.
        // Use HTTP error status code to simulate server failure.
        resolve([500, { success: false }]);
      }
    }, 1000);
  });
});

Composing from multiple sources with Promises:

const normalAxios = axios.create();
const mockAxios = axios.create();
const mock = new AxiosMockAdapter(mockAxios);

mock
  .onGet("/orders")
  .reply(() =>
    Promise.all([
      normalAxios.get("/api/v1/orders").then((resp) => resp.data),
      normalAxios.get("/api/v2/orders").then((resp) => resp.data),
      { id: "-1", content: "extra row 1" },
      { id: "-2", content: "extra row 2" },
    ]).then((sources) => [
      200,
      sources.reduce((agg, source) => agg.concat(source)),
    ])
  );

History

The history property allows you to enumerate existing axios request objects. The property is an object of verb keys referencing arrays of request objects.

This is useful for testing.

describe("Feature", () => {
  it("requests an endpoint", (done) => {
    const mock = new AxiosMockAdapter(axios);
    mock.onPost("/endpoint").replyOnce(200);

    feature
      .request()
      .then(() => {
        expect(mock.history.post.length).toBe(1);
        expect(mock.history.post[0].data).toBe(JSON.stringify({ foo: "bar" }));
      })
      .then(done)
      .catch(done.fail);
  });
});

You can clear the history with resetHistory

mock.resetHistory();