网络模拟与Mock:基于HarmonyOS开发调试技巧

举报
Jack20 发表于 2026/06/25 21:26:09 2026/06/25
【摘要】 网络模拟与Mock:开发调试技巧Mock用得好,后端没做完也能把前端开发完 一、背景与动机:为什么需要网络Mock?前端开发最头疼的事:等后端接口。产品说下周上线,后端说接口还在设计,前端只能干等——或者先写页面,等接口好了再对接,然后发现字段名不一样、数据结构不对,返工…Mock就是为了解决这个问题:并行开发:前端不用等后端,自己模拟数据边界测试:模拟各种边界情况(空数据、错误、超时)稳...

网络模拟与Mock:开发调试技巧

Mock用得好,后端没做完也能把前端开发完

一、背景与动机:为什么需要网络Mock?

前端开发最头疼的事:等后端接口

产品说下周上线,后端说接口还在设计,前端只能干等——或者先写页面,等接口好了再对接,然后发现字段名不一样、数据结构不对,返工…

Mock就是为了解决这个问题:

  1. 并行开发:前端不用等后端,自己模拟数据
  2. 边界测试:模拟各种边界情况(空数据、错误、超时)
  3. 稳定测试:不依赖真实接口,测试更稳定
  4. 演示Demo:给客户演示时不用担心接口挂掉

Mock 的几种方式

方式 优点 缺点 适用场景
硬编码Mock 简单直接 不灵活 快速验证
JSON文件Mock 易维护 需要重启 本地开发
Mock服务器 功能强大 需要部署 团队协作
拦截器Mock 无侵入 需要配置 项目集成

二、核心原理:Mock拦截机制

Mock的核心是拦截请求,返回模拟数据

flowchart TB
    A[发起请求] --> B{Mock开关?}
    
    B -->|开启| C{匹配Mock规则?}
    B -->|关闭| D[真实请求]
    
    C -->|匹配| E[返回Mock数据]
    C -->|不匹配| D
    
    D --> F[返回真实数据]
    
    E --> G[记录Mock日志]
    F --> H[记录真实日志]
    
    G --> I[业务处理]
    H --> I
    
    subgraph Mock规则匹配
        J[URL匹配] --> K[方法匹配]
        K --> L[参数匹配]
        L --> M[条件匹配]
    end
    
    classDef primary fill:#4A90E2,stroke:#2E5C8A,color:#fff
    classDef warning fill:#F5A623,stroke:#C17D10,color:#fff
    classDef error fill:#E74C3C,stroke:#C0392B,color:#fff
    classDef info fill:#7ED321,stroke:#5BA318,color:#fff
    
    class A,E,G,I primary
    class B,C info
    class D,F,H warning
    class J,K,L,M error

匹配流程

  1. 检查 Mock 开关
  2. 遍历 Mock 规则,匹配 URL、方法、参数
  3. 匹配成功则返回 Mock 数据
  4. 匹配失败则走真实请求

三、代码实战:完整Mock系统实现

示例1:Mock配置与类型定义

// network/mock/types.ets

/**
 * Mock数据类型
 */
export type MockData<T = any> = T | ((request: MockRequest) => T | Promise<T>);

/**
 * Mock请求信息
 */
export interface MockRequest {
  /** 请求URL */
  url: string;
  /** 请求方法 */
  method: string;
  /** 请求头 */
  headers: Record<string, string>;
  /** URL参数 */
  params: Record<string, any>;
  /** 请求体 */
  body: any;
}

/**
 * Mock响应配置
 */
export interface MockResponse<T = any> {
  /** 响应数据 */
  data: MockData<T>;
  /** HTTP状态码 */
  status?: number;
  /** 响应头 */
  headers?: Record<string, string>;
  /** 延迟(毫秒),模拟网络延迟 */
  delay?: number;
  /** 是否触发错误 */
  error?: string;
}

/**
 * Mock规则
 */
export interface MockRule {
  /** 规则ID */
  id: string;
  /** 匹配的URL(支持正则) */
  url: string | RegExp;
  /** 匹配的HTTP方法 */
  method?: string | string[];
  /** 匹配条件函数 */
  condition?: (request: MockRequest) => boolean;
  /** Mock响应 */
  response: MockResponse | ((request: MockRequest) => MockResponse | Promise<MockResponse>);
  /** 优先级(数字越大优先级越高) */
  priority?: number;
  /** 是否启用 */
  enabled?: boolean;
  /** 描述 */
  description?: string;
}

/**
 * Mock配置
 */
export interface MockConfig {
  /** 是否启用Mock */
  enabled: boolean;
  /** Mock规则列表 */
  rules: MockRule[];
  /** 全局延迟 */
  globalDelay?: number;
  /** Mock失败时是否回退到真实请求 */
  fallbackToReal?: boolean;
  /** 日志级别 */
  logLevel?: 'none' | 'error' | 'warn' | 'info' | 'debug';
}

示例2:Mock管理器

// network/mock/MockManager.ets
import { MockRule, MockRequest, MockResponse, MockConfig } from './types';

/**
 * Mock管理器
 * 管理Mock规则,匹配请求,返回Mock数据
 */
export class MockManager {
  private config: MockConfig;
  private rules: MockRule[] = [];

  constructor(config: MockConfig) {
    this.config = config;
    this.rules = config.rules;
  }

  /**
   * 添加Mock规则
   */
  addRule(rule: MockRule): void {
    this.rules.push(rule);
    // 按优先级排序
    this.rules.sort((a, b) => (b.priority || 0) - (a.priority || 0));
    
    this.log('info', `[MockManager] 添加规则: ${rule.id}`);
  }

  /**
   * 移除Mock规则
   */
  removeRule(ruleId: string): void {
    const index = this.rules.findIndex(r => r.id === ruleId);
    if (index > -1) {
      this.rules.splice(index, 1);
      this.log('info', `[MockManager] 移除规则: ${ruleId}`);
    }
  }

  /**
   * 启用/禁用规则
   */
  toggleRule(ruleId: string, enabled: boolean): void {
    const rule = this.rules.find(r => r.id === ruleId);
    if (rule) {
      rule.enabled = enabled;
      this.log('info', `[MockManager] 规则 ${ruleId} ${enabled ? '启用' : '禁用'}`);
    }
  }

  /**
   * 匹配Mock规则
   * @param request 请求信息
   * @returns 匹配的规则或null
   */
  match(request: MockRequest): MockRule | null {
    if (!this.config.enabled) {
      return null;
    }

    for (const rule of this.rules) {
      // 检查是否启用
      if (rule.enabled === false) {
        continue;
      }

      // 检查URL匹配
      if (!this.matchUrl(rule.url, request.url)) {
        continue;
      }

      // 检查方法匹配
      if (rule.method && !this.matchMethod(rule.method, request.method)) {
        continue;
      }

      // 检查自定义条件
      if (rule.condition && !rule.condition(request)) {
        continue;
      }

      // 匹配成功
      this.log('info', `[MockManager] 匹配成功: ${rule.id} -> ${request.url}`);
      return rule;
    }

    return null;
  }

  /**
   * 执行Mock响应
   * @param rule Mock规则
   * @param request 请求信息
   */
  async execute(rule: MockRule, request: MockRequest): Promise<MockResponse> {
    // 获取响应配置
    let response: MockResponse;
    
    if (typeof rule.response === 'function') {
      response = await rule.response(request);
    } else {
      response = rule.response;
    }

    // 应用全局延迟
    const delay = response.delay ?? this.config.globalDelay ?? 0;
    if (delay > 0) {
      await this.delay(delay);
      this.log('info', `[MockManager] 延迟 ${delay}ms`);
    }

    // 处理错误
    if (response.error) {
      throw new Error(response.error);
    }

    // 处理动态数据
    let data = response.data;
    if (typeof data === 'function') {
      data = await data(request);
    }

    this.log('debug', `[MockManager] Mock响应:`, data);

    return {
      ...response,
      data
    };
  }

  /**
   * 匹配URL
   * @private
   */
  private matchUrl(pattern: string | RegExp, url: string): boolean {
    if (typeof pattern === 'string') {
      // 支持通配符
      if (pattern.includes('*')) {
        const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
        return regex.test(url);
      }
      return url.includes(pattern);
    }
    return pattern.test(url);
  }

  /**
   * 匹配方法
   * @private
   */
  private matchMethod(pattern: string | string[], method: string): boolean {
    if (Array.isArray(pattern)) {
      return pattern.map(p => p.toUpperCase()).includes(method.toUpperCase());
    }
    return pattern.toUpperCase() === method.toUpperCase();
  }

  /**
   * 延迟函数
   * @private
   */
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  /**
   * 日志输出
   * @private
   */
  private log(level: string, message: string, ...args: any[]): void {
    const levels = ['none', 'error', 'warn', 'info', 'debug'];
    const configLevel = levels.indexOf(this.config.logLevel || 'info');
    const currentLevel = levels.indexOf(level);

    if (currentLevel <= configLevel) {
      console[level as 'error' | 'warn' | 'info' | 'debug'](`[Mock] ${message}`, ...args);
    }
  }

  /**
   * 获取所有规则
   */
  getRules(): MockRule[] {
    return this.rules;
  }

  /**
   * 更新配置
   */
  updateConfig(config: Partial<MockConfig>): void {
    this.config = { ...this.config, ...config };
  }
}

示例3:Mock拦截器

// network/mock/MockInterceptor.ets
import { Interceptor, Response, RequestConfig } from '../types';
import { MockManager } from './MockManager';
import { MockRequest } from './types';

/**
 * Mock拦截器
 * 拦截请求,返回Mock数据
 */
export class MockInterceptor implements Interceptor {
  private mockManager: MockManager;

  constructor(mockManager: MockManager) {
    this.mockManager = mockManager;
  }

  /**
   * 请求拦截:检查是否需要Mock
   */
  async beforeRequest(config: RequestConfig): Promise<RequestConfig> {
    // 构建Mock请求信息
    const mockRequest: MockRequest = {
      url: config.url,
      method: config.method || 'GET',
      headers: config.headers || {},
      params: config.params || {},
      body: config.data
    };

    // 匹配Mock规则
    const rule = this.mockManager.match(mockRequest);

    if (rule) {
      // 标记为Mock请求
      (config as any).__mock = true;
      (config as any).__mockRule = rule;
      (config as any).__mockRequest = mockRequest;
    }

    return config;
  }

  /**
   * 响应拦截:返回Mock数据
   */
  async afterResponse<T>(response: Response<T>): Promise<Response<T>> {
    const config = response.config;
    const isMock = (config as any).__mock;

    if (!isMock) {
      return response;
    }

    // 获取Mock规则和请求信息
    const rule = (config as any).__mockRule;
    const mockRequest = (config as any).__mockRequest;

    try {
      // 执行Mock响应
      const mockResponse = await this.mockManager.execute(rule, mockRequest);

      // 返回Mock数据
      return {
        data: mockResponse.data as T,
        status: mockResponse.status || 200,
        headers: mockResponse.headers || {},
        config: config
      };
    } catch (error) {
      // Mock错误
      throw error;
    }
  }
}

示例4:常用Mock规则定义

// network/mock/rules/index.ets
import { MockRule } from '../types';

/**
 * 用户相关Mock规则
 */
export const userMockRules: MockRule[] = [
  // 登录接口
  {
    id: 'user-login',
    url: '/auth/login',
    method: 'POST',
    response: (request) => {
      const { username, password } = request.body;
      
      // 模拟验证
      if (username === 'admin' && password === '123456') {
        return {
          data: {
            code: 0,
            message: '登录成功',
            data: {
              token: 'mock_token_' + Date.now(),
              user: {
                id: '1',
                username: 'admin',
                nickname: '管理员',
                avatar: 'https://example.com/avatar.jpg'
              }
            }
          },
          status: 200
        };
      } else {
        return {
          data: {
            code: 1001,
            message: '用户名或密码错误',
            data: null
          },
          status: 200
        };
      }
    },
    description: '用户登录'
  },

  // 获取用户信息
  {
    id: 'user-info',
    url: '/user/current',
    method: 'GET',
    response: {
      data: {
        code: 0,
        message: 'success',
        data: {
          id: '1',
          username: 'mock_user',
          nickname: 'Mock用户',
          email: 'mock@example.com',
          avatar: 'https://example.com/avatar.jpg',
          createdAt: '2024-01-01'
        }
      },
      status: 200,
      delay: 500 // 模拟500ms延迟
    },
    description: '获取当前用户信息'
  }
];

/**
 * 商品相关Mock规则
 */
export const productMockRules: MockRule[] = [
  // 商品列表
  {
    id: 'product-list',
    url: '/products',
    method: 'GET',
    response: (request) => {
      const page = request.params.page || 1;
      const size = request.params.size || 10;
      
      // 生成模拟商品数据
      const products = Array.from({ length: size }, (_, i) => ({
        id: `${(page - 1) * size + i + 1}`,
        name: `商品 ${(page - 1) * size + i + 1}`,
        price: Math.random() * 1000,
        image: `https://picsum.photos/200/200?random=${(page - 1) * size + i}`,
        stock: Math.floor(Math.random() * 100),
        category: ['电子产品', '服装', '食品'][i % 3]
      }));

      return {
        data: {
          code: 0,
          message: 'success',
          data: {
            list: products,
            total: 100,
            page,
            size
          }
        },
        status: 200
      };
    },
    description: '商品列表'
  },

  // 商品详情
  {
    id: 'product-detail',
    url: /\/products\/\d+/, // 正则匹配
    method: 'GET',
    response: (request) => {
      const id = request.url.split('/').pop();
      
      return {
        data: {
          code: 0,
          message: 'success',
          data: {
            id,
            name: `商品 ${id}`,
            price: 99.9,
            image: `https://picsum.photos/400/400?random=${id}`,
            description: '这是一个模拟的商品描述',
            stock: 100,
            category: '电子产品',
            specs: [
              { name: '颜色', value: '黑色' },
              { name: '尺寸', value: 'M' }
            ]
          }
        },
        status: 200
      };
    },
    description: '商品详情'
  }
];

/**
 * 错误场景Mock规则
 */
export const errorMockRules: MockRule[] = [
  // 模拟网络超时
  {
    id: 'error-timeout',
    url: '/test/timeout',
    response: {
      data: null,
      error: '请求超时',
      delay: 60000 // 60秒超时
    },
    description: '模拟超时'
  },

  // 模拟服务器错误
  {
    id: 'error-500',
    url: '/test/error500',
    response: {
      data: {
        code: 500,
        message: '服务器内部错误',
        data: null
      },
      status: 500
    },
    description: '模拟500错误'
  },

  // 模拟空数据
  {
    id: 'empty-data',
    url: '/test/empty',
    response: {
      data: {
        code: 0,
        message: 'success',
        data: []
      },
      status: 200
    },
    description: '模拟空数据'
  },

  // 模拟网络断开
  {
    id: 'network-off',
    url: '/test/offline',
    response: {
      data: null,
      error: '网络不可用'
    },
    description: '模拟网络断开'
  }
];

// 导出所有规则
export const allMockRules: MockRule[] = [
  ...userMockRules,
  ...productMockRules,
  ...errorMockRules
];

示例5:Mock开关与配置

// network/mock/config.ets
import { MockConfig } from './types';
import { allMockRules } from './rules';

/**
 * 获取Mock配置
 * 根据环境变量决定是否启用Mock
 */
export function getMockConfig(): MockConfig {
  // 从环境变量或配置文件读取
  const isDevelopment = true; // 实际项目中应该从环境变量读取
  const mockEnabled = isDevelopment; // 开发环境启用Mock

  return {
    enabled: mockEnabled,
    rules: allMockRules,
    globalDelay: 300, // 全局延迟300ms,模拟真实网络
    fallbackToReal: true, // Mock失败时回退到真实请求
    logLevel: 'info'
  };
}

/**
 * 初始化Mock系统
 */
export function initMock(): MockManager {
  const config = getMockConfig();
  const manager = new MockManager(config);

  console.info('[Mock] Mock系统初始化完成');
  console.info(`[Mock] 启用状态: ${config.enabled}`);
  console.info(`[Mock] 规则数量: ${config.rules.length}`);

  return manager;
}

示例6:开发调试面板

// components/MockDebugPanel.ets
import { MockManager } from '../network/mock/MockManager';
import { MockRule } from '../network/mock/types';

/**
 * Mock调试面板
 * 用于开发时查看和管理Mock规则
 */
@Component
export struct MockDebugPanel {
  @State mockManager: MockManager | null = null;
  @State rules: MockRule[] = [];
  @State expanded: boolean = false;

  aboutToAppear() {
    // 获取Mock管理器实例
    // this.mockManager = getMockManager();
    if (this.mockManager) {
      this.rules = this.mockManager.getRules();
    }
  }

  build() {
    Column() {
      // 标题栏
      Row() {
        Text('Mock调试面板')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .layoutWeight(1)

        Toggle({ type: ToggleType.Switch, isOn: this.expanded })
          .onChange((isOn) => {
            this.expanded = isOn;
          })
      }
      .width('100%')
      .height(48)
      .padding({ left: 16, right: 16 })
      .backgroundColor('#F5F5F5')

      // 规则列表
      if (this.expanded) {
        List() {
          ForEach(this.rules, (rule: MockRule) => {
            ListItem() {
              this.ruleItem(rule);
            }
          })
        }
        .width('100%')
        .height(300)
        .backgroundColor(Color.White)
      }
    }
    .width('100%')
    .backgroundColor('#F5F5F5')
    .borderRadius(8)
  }

  /**
   * 规则项
   */
  @Builder
  ruleItem(rule: MockRule) {
    Row() {
      Column() {
        Text(rule.id)
          .fontSize(14)
          .fontWeight(FontWeight.Bold)

        Text(rule.description || rule.url.toString())
          .fontSize(12)
          .fontColor('#999999')
          .margin({ top: 4 })
      }
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Start)

      Toggle({ type: ToggleType.Switch, isOn: rule.enabled !== false })
        .onChange((isOn) => {
          if (this.mockManager) {
            this.mockManager.toggleRule(rule.id, isOn);
          }
        })
    }
    .width('100%')
    .padding(12)
    .border({ width: { bottom: 1 }, color: '#EEEEEE' })
  }
}

四、踩坑与注意事项

坑1:Mock数据与真实数据结构不一致

问题:Mock数据字段名和真实接口不一样,对接时返工。

解决:Mock数据严格按照接口文档定义:

// ✅ 正确:根据接口文档定义
interface UserResponse {
  code: number;
  message: string;
  data: {
    id: string;
    username: string;
    nickname: string;
  };
}

const mockUser: UserResponse = {
  code: 0,
  message: 'success',
  data: {
    id: '1',
    username: 'mock_user',
    nickname: 'Mock用户'
  }
};

坑2:忘记关闭Mock

问题:上线时忘记关闭Mock,生产环境返回假数据。

解决:通过环境变量控制:

// 根据环境变量自动判断
const mockEnabled = process.env.NODE_ENV === 'development';

// 或者通过构建配置
const mockEnabled = __DEV__; // 构建时注入

坑3:Mock延迟太短

问题:Mock延迟设为0,页面加载太快,Loading状态一闪而过,看不出效果。

解决:设置合理的延迟:

// 开发环境设置延迟,模拟真实网络
const globalDelay = __DEV__ ? 300 : 0;

坑4:Mock规则优先级冲突

问题:多个规则匹配同一个URL,不知道用哪个。

解决:设置优先级,优先级高的先匹配:

// 优先级:数字越大越优先
const rules = [
  { id: 'detail', url: '/products/:id', priority: 10 }, // 详情
  { id: 'list', url: '/products', priority: 5 }         // 列表
];

五、HarmonyOS 6 适配要点

1. 环境变量支持

// HarmonyOS 6 支持在构建时注入环境变量
// build-profile.json5
{
  "app": {
    "buildOption": {
      "arkOptions": {
        "compilerOptions": {
          "defines": {
            "MOCK_ENABLED": true,
            "API_BASE_URL": "https://api.example.com"
          }
        }
      }
    }
  }
}

// 代码中使用
const mockEnabled = MOCK_ENABLED; // 构建时替换

2. 配置文件支持

// HarmonyOS 6 支持读取配置文件
import { resources } from '@kit.LocalizationKit';

// 读取rawfile中的配置
const mockConfig = await resources.getRawFileContent('mock/config.json');
const config = JSON.parse(mockConfig);

3. 热重载支持

// HarmonyOS 6 支持Mock规则热重载
// 修改Mock数据后无需重启应用
import { emitter } from '@kit.BasicServicesKit';

// 监听Mock配置变化
emitter.on({ eventId: 1001 }, () => {
  reloadMockRules();
});

六、总结

Mock是前端开发的"独立武器",让开发不再依赖后端:

Mock方式 适用场景 推荐度
硬编码 快速验证 ⭐⭐
JSON文件 本地开发 ⭐⭐⭐
Mock服务器 团队协作 ⭐⭐⭐⭐
拦截器Mock 项目集成 ⭐⭐⭐⭐⭐

记住几个原则:

  • ✅ Mock数据要符合接口文档
  • ✅ 生产环境必须关闭Mock
  • ✅ 设置合理延迟,模拟真实网络
  • ✅ 规则要有优先级,避免冲突
  • ✅ 提供调试面板,方便管理

下一篇我们深入网络安全加固,看看如何防抓包与数据加密。


💡 最佳实践提示:建议在项目中创建 mock/ 目录,按模块组织Mock规则,方便维护和团队协作。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。