Stub是返回预设值的哑巴替身,仅让代码运行而不验证调用;Mock可记录并验证调用行为;Fake是具真实逻辑的轻量实现;Spy是带参数记录能力的Stub增强版。

Stub 是只返回预设值的“哑巴”替身
Stub 的核心作用是让被测代码能跑起来,不关心它是否被调用、怎么被调用,只负责在指定方法被调用时返回你硬编码的值。它没有行为验证能力。
- 典型场景:
HttpClient调用外部 API 时,用Stub返回固定 JSON 字符串,避免网络依赖 - 常见错误:把
Stub当成Mock用,试图验证调用次数或参数 —— 它根本不记录这些 - 实现方式:手写类继承接口并重写方法,或用
Moq的Setup+Returns(但不配Verify) - 示例:
mock.Setup(x => x.GetStatus()).Returns("OK");—— 这本质是 Stub 行为
Mock 是带行为断言的“考官”替身
Mock 的关键特征是「可验证」:它会记录调用痕迹,并允许你在断言阶段检查是否被调用、调用几次、参数是否匹配。它既提供返回值,也承担验证职责。
- 典型场景:测试一个订单服务是否在库存不足时调用了
NotifyAdmin(),且只调一次 - 容易踩坑:过度使用
Verify导致测试脆弱 —— 比如验证了内部调用顺序,实际重构时逻辑没变但调用路径变了,测试就挂 - 参数差异:
Moq中Setup定义响应,Verify执行断言;NSubstitute则用Received(1)风格 - 性能影响:所有调用都会被拦截和记录,大量 Mock 可能拖慢单元测试执行速度
Fake 是有真实逻辑但轻量的“简化版”实现
Fake 不是空壳,而是用内存集合、简单算法等替代真实依赖(比如数据库、文件系统),它自己能工作,只是绕过了外部副作用。
- 典型场景:用
InMemoryDatabase替代SqlServer测试 EF Core 仓储层 - 常见误解:以为 Fake 就是 “随便写个假类”,其实它需要保持与真实实现一致的契约和基本正确性(比如
FakeRepository的Save要真存进 List,GetById要真能查出来) - 兼容性注意:Fake 往往不具备完整功能 —— 比如内存数据库不支持复杂 SQL 或事务隔离级别,测试覆盖不到边界情况
- 和 Stub 区别:Stub 是“静态应答”,Fake 是“可运行的最小闭环”
Spy 是带记录能力的“监听型”Stub
Spy 本质是 Stub 的增强版:它返回预设值,同时悄悄记下谁调了它、传了什么参数、调了多少次,供后续断言。它不主动抛异常或控制流程,只“看”不“判”。
- 典型场景:验证某个日志服务是否被传入了预期的错误消息,但不关心它内部怎么处理
- Moq 中没有原生 Spy 类型,但可通过
Callback实现:mock.Setup(x => x.Log(It.IsAny())).Callback (msg => capturedMsg = msg); - 和 Mock 关键区别:Spy 不自带
Verify方法,你需要自己保存状态并在Assert阶段手动比对;Mock 把记录+验证打包好了 - 容易忽略的点:Spy 的回调里不要做耗时或副作用操作(比如发 HTTP 请求),否则测试会不可靠










