依赖注入是通过参数传入服务而非内部创建,如构造函数接收api、logger,函数接收httpclient;将副作用抽象为可替换输入,如clock.now()、random.next()、storage.get();用jest/vitest精准mock外部模块,确保接口契约一致。

让依赖“进来”,而不是“自己找”
依赖注入的本质是把函数或类需要的服务,通过参数传入,而不是在内部直接创建或引用全局实例。比如一个用户服务不该自己 new ApiClient(),而应该接收一个 apiClient 参数。这样测试时就能传入 mock 对象,完全绕过真实网络请求。
用构造函数或函数参数显式接收依赖
这是最直接、最可控的方式,不需要引入复杂容器:
- 类写法:构造函数接收依赖,如 constructor(api, logger),测试时 new UserService(mockApi, mockLogger)
- 函数写法:关键服务作为参数,如 function fetchProfile(id, httpClient),测试时传入返回固定 Promise 的 mock 函数
- 避免模块顶层初始化,比如不要写 const client = createClient(),改用工厂函数 createApiClient(config),便于测试时注入不同行为
把副作用变成可替换的输入
时间、随机数、本地存储、fetch 这些不是“逻辑”,而是“环境能力”。它们必须抽象出来,才能被控制:
- 把 new Date() 换成 clock.now(),测试时用 vi.setSystemTime() 或传入固定日期
- 把 Math.random() 换成 random.next(),测试时返回预设值
- 把 localStorage.getItem() 封装为 storage.get(),测试时替换为内存 Map 实现
- 所有 I/O 操作(包括 fetch)都走统一的 httpClient 接口,不硬编码调用
用 Jest/Vitest 精准 Mock 外部模块
Mock 不是越多越好,关键是拦截真实副作用发生的点:
- 在 import 之前 调用 jest.mock('axios'),确保后续所有导入都拿到 mock 版本
- 为方法设定行为:axios.get.mockResolvedValue({ data: { id: 1 } })
- 断言调用是否符合预期:expect(axios.post).toHaveBeenCalledWith('/login', { user: 'a' })
- 浏览器 API 如 fetch 或 Date.now 可直接重写 global.fetch 或用 jest.useFakeTimers()
接口契约比具体实现更重要
只要 mock 对象满足所需方法签名(比如有 .get() 和 .post()),业务逻辑就无需修改。这意味着:
- 测试时用轻量对象模拟,生产时换真实 HTTP 客户端
- 开发时用内存存储代替 localStorage,上线再切回持久化
- 日志、通知等辅助功能也接受回调函数,而非直接调用 console.log
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











