moq 是 .net 中最轻量、语法最直观的 mock 框架,适合初学者;必须用 .object 获取代理实例,方法用 setup()、只读属性用 setupget()、异步用 returnsasync(),参数匹配推荐 it.isany() 和 it.is(),验证需显式指定 times。

Moq 不是“必须用”的 Mock 框架,但它是 .NET 生态中最轻量、语法最直观、与 xUnit/NUnit 集成最顺的选项——尤其适合刚写单元测试、又不想被复杂生命周期和配置绊住的人。
Mock 不能直接 new,必须用 .Object 获取实例
错误写法:var mock = new Mock<irepository>(); SomeMethod(mock);</irepository> → 编译失败,报错 Cannot convert from 'Mock<irepository>' to 'IRepository'</irepository>。因为 Mock<t></t> 是个包装器,背后是运行时动态代理,不是真实接口类型。
正确做法是显式取 .Object 属性:SomeMethod(mock.Object);
- 漏掉
.Object是新手最常卡住的地方,IDE 通常不会高亮提示,只在编译时报类型不匹配 - 如果你在构造函数注入时传了
mock而非mock.Object,被测类会因类型不符根本无法创建 -
mock.Object返回的是实现了目标接口的代理实例,它能响应你Setup过的行为
Setup() 和 SetupGet() 必须按成员类型严格区分
模拟方法调用用 Setup(),模拟只读属性访问用 SetupGet(),混用会导致静默失效或抛 NotSupportedException。
例如:
✅ 正确:mock.Setup(x => x.GetById(123)).Returns(new User());
✅ 正确:mock.SetupGet(x => x.Name).Returns("Alice");
❌ 错误:mock.Setup(x => x.Name).Returns("Alice");(Name 是属性,不是方法)
- 可写属性的
set用SetupSet(),但单元测试中应避免验证 setter 被调用,而应验证状态变更结果 - 如果对属性用了
Setup却没生效,八成是该用SetupGet或SetupSet -
SetupGet对 auto-property 有效;若属性有 backing field 且逻辑复杂,Moq 无法拦截,需考虑重构为接口或虚方法
参数匹配别硬编码,用 It.IsAny() 和 It.Is()
写死参数如 Setup(x => x.Find("admin")) 极易翻车:大小写、空格、null、浮点误差都会导致匹配失败,返回默认值(null 或 false),测试看似通过实则没覆盖逻辑。
推荐写法:
mock.Setup(x => x.Find(It.IsAny<string>())).Returns(...);</string> —— 忽略参数值,任意字符串都命中
mock.Setup(x => x.Save(It.Is<user>(u => u.Id > 0))).Returns(true);</user> —— 自定义条件校验
mock.Setup(x => x.Log(It.IsAny<string>())).Callback<string>(s => calls.Add(s));</string></string> —— 带副作用,比如记录调用内容
- 变量捕获(
string key = "cacheKey"; mock.Setup(x => x.Get(key)))不被支持,必须改用It.Is或It.IsAny - 多个
Setup注册顺序决定匹配优先级,后注册的不会覆盖前注册的;需要不同入参不同返回时,靠It.Is区分 - 异步方法(
Task<t></t>)必须用ReturnsAsync(),不能用Returns(Task.FromResult(...)),否则类型推导失败
Verify() 不带 Times 就等于没断言
默认 mock.Verify(x => x.Send("a@b.com", "Welcome!")) 等价于 Times.AtLeastOnce(),但实际报错信息模糊:Expected invocation on the mock once, but was 0 times,不告诉你哪次调用缺失。
必须显式指定频次:
mock.Verify(x => x.Send("a@b.com", "Welcome!"), Times.Once());
mock.Verify(x => x.Log(It.Is<string>(s => s.Contains("ERROR"))), Times.Exactly(2));</string>
- 不带参数的
mock.Verify()会检查所有已Setup的成员是否被调用,容易导致过度断言,慎用 - 验证异步方法调用时,确保
Act阶段用await,而非.Result或.Wait(),否则可能死锁 - xUnit 每个测试方法新建实例,
mock必须在每个[Fact]内创建或通过IClassFixture共享,不能声明为static字段
最常被忽略的点是:Mock 只对接口和 virtual 成员生效。密封类、static 方法、internal 成员、非 virtual 实例方法,Moq 一律无法拦截——这时候不是 Moq 不行,而是设计上就不该被 mock,得考虑重构依赖或换用集成测试。











