“前端单元测试的救星:Vitest 输入和 Mock 技术详解

推荐阅读:

Vitest 单元测试:输入和 Mock 技术

写单元测试时,你是不是遇到过这样的困扰:测试今天通过了,明天就莫名其妙地失败了?很可能是因为你的代码依赖了一些"不听话"的外部数据。今天我们就来聊聊如何驯服这些捣蛋的间接输入。

直接输入和间接输入分别是什么?

想象你在做菜,有两种获取食材的方式:

直接输入:你直接把食材拿在手里传给厨师

function cookDish(ingredient) {
  // ingredient 就是直接输入
  return `美味的${ingredient}料理`;
}

间接输入:厨师需要去冰箱、菜市场或者找其他人要食材

function cookDish() {
  const ingredient = getFromFridge(); // 这就是间接输入
  return `美味的${ingredient}料理`;
}

在编程中,间接输入就像是:

  • 从网上获取天气数据
  • 从数据库查询用户信息
  • 读取配置文件
  • 调用其他系统的接口

间接输入的麻烦事

间接输入麻烦就像是:你想测试厨师的手艺,但每次食材都不一样。今天冰箱里是鸡蛋,明天可能是土豆,后天可能啥都没有!

// 有问题的测试
function getUserGreeting(userId) {
  const user = getUserFromDatabase(userId); // 数据库里的数据可能变化
  return `Hello ${user.name}!`;
}

test('应该返回问候语', () => {
  const result = getUserGreeting(1);
  expect(result).toBe('Hello 小明!'); // 如果数据库里用户名改了怎么办?
});

这种测试就是"脆弱测试"——就像玻璃一样,轻轻一碰就碎。

Mock:给你一个假的替身

Mock 就像是给你的代码找了个"替身演员"。真正的演员可能今天心情不好、明天生病了,但替身演员永远按剧本演出。

基础 Mock:vi.mock

import { vi } from 'vitest';

// 告诉测试框架:"嘿,用这个假的替代真的数据库查询"
vi.mock('./database', () => ({
  getUserFromDatabase: vi.fn(() => ({ name: '小明' })), // 永远返回小明
}));

test('应该返回问候语', () => {
  const result = getUserGreeting(1);
  expect(result).toBe('Hello 小明!'); // 现在结果可预测了!
});

就像是说:“不管真实数据库里有什么,我的测试中永远假装有个叫小明的用户。”

高级技巧:vi.doMock

有时候你需要更灵活的控制,就像导演说:“这场戏需要一个高个子演员,下场戏需要一个矮个子演员。

describe('不同用户的测试', () => {
  test('VIP用户应该有特殊问候', async () => {
    // 这场戏的演员是VIP用户
    vi.doMock('./database', () => ({
      getUserFromDatabase: vi.fn(() => ({ name: '王总', isVip: true })),
    }));

    const { getUserGreeting } = await import('./greeting');
    const result = getUserGreeting(1);
    expect(result).toBe('尊敬的王总,欢迎回来!');
  });

  test('普通用户应该有普通问候', async () => {
    // 这场戏的演员是普通用户
    vi.doMock('./database', () => ({
      getUserFromDatabase: vi.fn(() => ({ name: '小李', isVip: false })),
    }));

    const { getUserGreeting } = await import('./greeting');
    const result = getUserGreeting(1);
    expect(result).toBe('Hello 小李!');
  });
});

处理异步情况:等等,数据还在路上

现实中很多数据需要时间获取,就像网购要等快递一样:

// 模拟网络请求
async function getWeatherGreeting(city) {
  const weather = await fetchWeatherFromAPI(city); // 需要等待
  return `今天${city}的天气是${weather.condition}`;
}

// 测试异步间接输入
test('应该显示天气信息', async () => {
  vi.doMock('./weatherAPI', () => ({
    fetchWeatherFromAPI: vi.fn(
      () => Promise.resolve({ condition: '晴天' }) // 假装API返回晴天
    ),
  }));

  const { getWeatherGreeting } = await import('./weather');
  const result = await getWeatherGreeting('北京'); // 记得用 await
  expect(result).toBe('今天北京的天气是晴天');
});

实用小贴士

  1. 保持测试环境干净
beforeEach(() => {
  vi.resetModules(); // 每次测试前清理,就像每场戏前重置舞台
});
  1. 选择合适的 Mock 方式
  • vi.mock:适合整个测试文件都用同样的假数据
  • vi.doMock:适合每个测试需要不同的假数据
  1. 让测试更易懂
// 好的做法:给假数据起有意义的名字
const mockUserData = { name: '测试用户', age: 25 };
vi.fn(() => mockUserData);

// 不好的做法:直接写死数据
vi.fn(() => ({ name: 'test', age: 25 }));

总结:

  • 🎭 替身演员:替代不可控的外部依赖
  • 🎬 导演控制:你决定"演员"说什么、做什么
  • 🔄 重复演出:每次测试都能得到相同的结果
  • ⚡ 快速响应:不需要真的去网络请求或查数据库

通过合理使用 Mock,你的测试会变得:

  • 更稳定(不会因为外部数据变化而失败)
  • 更快速(不需要真实的网络请求)
  • 更可控(你说了算!)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值