Moq 是 .NET 中最轻量直观的 Mock 框架,使用需注意:必须通过 .Object 获取模拟实例;Setup() 与 SetupGet() 不可混用;参数匹配应使用 It.IsAny() 或 It.Is();异步方法需严格匹配返回类型如 ReturnsAsync。

Moq 不是“必须用”的 Mock 框架,但它是 .NET 生态中最轻量、语法最直观、与 xUnit/NUnit 集成最顺的选项——尤其适合刚写单元测试、又不想被复杂生命周期和配置绊住的人。
Mock 不能直接 new,必须用 .Object
你不能把 Mock<irepository></irepository> 当作 IRepository 传给被测类。它只是个包装器,背后是运行时动态代理,不是真实接口实例。
- 错误写法:
var mock = new Mock<irepository>(); SomeMethod(mock);</irepository>→ 编译失败,报错Cannot convert from 'Mock<irepository>' to 'IRepository'</irepository> - 正确写法:
SomeMethod(mock.Object);→.Object才是模拟出的接口实例 - 漏掉
.Object是新手卡住最多的地方,别在构造函数或属性注入时传错类型
Setup() 和 SetupGet() 别混用
Moq 对方法调用和属性访问的模拟逻辑完全不同,混用会导致静默失效或抛 NotSupportedException。
- 模拟方法:
mock.Setup(x => x.GetById(123)).Returns(new User()); - 模拟只读属性:
mock.SetupGet(x => x.Name).Returns("Alice");(注意不是Setup(x => x.Name)) - 模拟可写属性的 set:用
SetupSet,但极少需要;单元测试应验证状态结果,而非 setter 被调用 - 如果对属性用了
Setup却没生效,八成是该用SetupGet或SetupSet
参数匹配必须用 It.IsAny() 或 It.Is()
硬编码参数值会因浮点误差、引用相等、null 判断翻车;Moq 匹配靠表达式树,不支持变量捕获。
- 安全写法:
mock.Setup(x => x.Find(It.Is<string>(s => s.StartsWith("A")))).Returns(...);</string> - 忽略参数:
mock.Setup(x => x.Save(It.IsAny<user>())).Returns(true);</user> - 带副作用:
mock.Setup(x => x.Log(It.IsAny<string>())).Callback<string>(s => calls.Add(s));</string></string> - 别用
Setup(x => x.GetById(123))覆盖Setup(x => x.GetById(456))—— Moq 按注册顺序匹配,后注册的不会覆盖前注册的
异步方法 Mock 必须匹配返回类型
Task<T>、Task、ValueTask<T> 的处理方式完全不同,类型不匹配会导致 Setup 静默失败或编译报错。
-
Task<User>方法:用ReturnsAsync(new User()) -
Task(无返回):用Returns(Task.CompletedTask) -
ValueTask<User>:必须用Returns(new ValueTask<user>(new User()))</user>或Returns(ValueTask.FromResult(new User())) - 别在 Act 阶段用
.Result或.Wait():xUnit 异步测试中这会死锁
最容易被忽略的是:Mock 对象本身不会自动替换依赖——你得确保被测类构造时接收的是 mock.Object,而不是 new 出来的实现类;DI 容器注册也得用 AddSingleton<IInterface>(mock.Object),否则一切 Setup 都白搭。


















