☰
Moq在.NET单元测试中的高效应用与工程实践
2026/10/9 16:08:09 网站建设 项目流程

1. 为什么Moq不是“又一个Mock框架”,而是.NET测试工程化的分水岭

在某公司重构遗留订单系统时,我接手过一个典型场景:核心服务依赖外部支付网关、库存中心和用户画像API,三个接口全部走HTTP调用。单元测试跑一次要等8秒——不是因为逻辑复杂,而是每次都要真实发起网络请求、等待超时或响应。更糟的是,当库存中心临时维护,所有测试直接挂掉,CI流水线卡在“测试失败”环节,开发被迫切到集成环境调试,整个迭代节奏被拖垮。这种“测试即运行”的反模式,在.NET生态里曾是普遍痛点。直到Moq出现,它没发明新概念,却把mock这个抽象动作,变成了像写if语句一样直觉的操作。

Moq的核心价值,从来不是“能模拟对象”,而是让测试代码获得与生产代码对等的表达力与控制权。它不强制你写一堆模板代码去继承Mock<T>类,也不要求你提前声明几十个虚方法——它用Lambda表达式直接描述“当调用A方法且参数是X时,返回Y”。这种设计背后是.NET对表达式树(Expression Tree)的深度支持:Moq在运行时解析你的Lambda,提取出方法名、参数类型、返回值约束,再动态生成代理类。这比Java的Mockito靠反射+字节码增强的方案更轻量,也比早期Rhino Mocks依赖Expect.Call()那种命令式写法更符合C#的函数式演进方向。

关键词“Moq”“.NET测试套件”“高效”在这标题里不是修饰词,而是三个硬性指标:Moq是工具选型的唯一解(非它不可),.NET测试套件是落地场景(不是Java或Python项目),高效是结果验证标准(毫秒级执行、零外部依赖、可重复验证)。这意味着本文不会讨论“Moq vs NSubstitute”的哲学辩论,也不会教你怎么给一个密封类打补丁——那些属于边缘场景。我们要解决的是:如何用Moq把一个原本需要30分钟才能跑完的测试集,压缩到2.3秒内完成,且每个测试用例的意图清晰到能让新同事一眼看懂业务规则。

我见过太多团队把Moq当“胶水”用:为每个依赖都建一个Mock<IService>,然后在Setup里堆砌.Returns(),最后在Verify里检查调用次数。结果测试代码比业务代码还长,改一行业务逻辑就要同步修改五处测试。这不是Moq的问题,是没理解它的设计契约——Moq的Setup不是配置,而是契约声明;Verify不是断言,而是行为审计。真正的高效,始于把测试从“验证实现细节”转向“验证业务契约”。比如订单创建流程,重点不该是“是否调用了库存服务的DecreaseStock方法”,而是“当库存不足时,订单状态必须标记为PendingReview”。Moq让你能精准锚定这个契约点,而不是在技术实现的迷宫里兜圈。

2. Moq底层机制拆解:为什么它能绕过编译器限制直接操作虚方法

2.1 表达式树解析:从Lambda到IL指令的翻译过程

当你写下mock.Setup(x => x.GetPrice("SKU-123")).Returns(99.9m),Moq做的第一件事不是执行这个Lambda,而是把它编译成Expression<Func<ProductService, decimal>>。这个表达式树的结构是:根节点是Lambda表达式,子节点是MethodCallExpression(代表GetPrice调用),再往下是ConstantExpression("SKU-123"字符串)。Moq通过递归遍历这棵树,提取出关键元数据:

  • 方法名:GetPrice
  • 参数类型数组:typeof(string)
  • 返回类型:typeof(decimal)
  • 参数值快照:"SKU-123"

提示:Moq只支持表达式树能捕获的信息。所以mock.Setup(x => x.Price).Returns(99.9m)会报错——Price是属性,没有方法调用节点,无法生成调用契约。正确写法是mock.SetupGet(x => x.Price).Returns(99.9m),这里Moq专门提供了SetupGet/SetupSet方法来处理属性访问。

这个解析过程发生在测试初始化阶段(通常是[TestInitialize]方法里),耗时微乎其微(纳秒级)。但它的意义在于:Moq此时已经知道“你要拦截哪个方法、用什么参数、期望什么返回值”,接下来就是动态生成代理类。

2.2 动态代理生成:Castle.Core如何在内存中构建新类型

Moq底层依赖Castle.Core的DynamicProxy库。当解析完表达式树后,Moq会调用ProxyGenerator.CreateClassProxy或CreateInterfaceProxy。以接口IInventoryService为例,DynamicProxy实际生成的类类似这样:

public class InventoryServiceProxy : IInventoryService { private readonly MockRepository _repository; public InventoryServiceProxy(MockRepository repository) { _repository = repository; } public bool DecreaseStock(string sku, int quantity) { // 关键:这里不是硬编码逻辑,而是查Moq的调用记录表 var call = new MethodInvocation( method: typeof(IInventoryService).GetMethod("DecreaseStock"), args: new object[] { sku, quantity } ); return (bool)_repository.Resolve(call); } }

这个代理类完全在内存中生成,不写入磁盘,也不需要提前编译。DynamicProxy利用.NET的System.Reflection.Emit命名空间,直接拼装IL指令。比如callvirt指令调用虚方法,ldarg.0加载this指针——这些IL指令被写入动态模块,再通过TypeBuilder.CreateType()生成最终类型。整个过程比传统反射调用快10倍以上,因为避免了每次调用都要解析方法签名的开销。

2.3 调用匹配引擎:为什么Setup(x => x.Process("A"))不匹配Process("a")

Moq的匹配逻辑远比表面看起来严格。它默认使用值相等(Value Equality)而非引用相等。当你写Setup(x => x.Process("A")),Moq内部存储的是参数的快照值"A",并在每次实际调用时用Equals()比较。这意味着:

  • mock.Object.Process("A")→ 匹配成功
  • mock.Object.Process("a")→ 匹配失败(大小写敏感)
  • mock.Object.Process(null)→ 抛出ArgumentNullException(因为"A".Equals(null)返回false)

但Moq提供了It.Is<T>()来覆盖默认行为:

// 允许任意非空字符串 mock.Setup(x => x.Process(It.Is<string>(s => !string.IsNullOrEmpty(s)))) .Returns(true); // 允许以"SKU-"开头的字符串 mock.Setup(x => x.Process(It.Is<string>(s => s.StartsWith("SKU-")))) .Returns(true);

It.Is<T>的本质是把匹配逻辑封装成委托,在调用时动态执行。这比预存参数值更灵活,但也带来性能损耗——每次调用都要执行委托。实测数据显示,当It.Is逻辑包含正则表达式时,单次调用耗时从0.02ms升至0.15ms。所以我的经验是:简单相等用默认匹配,复杂规则才上It.Is,且避免在高频调用路径(如循环内)使用。

2.4 Verify机制:从“调用计数”到“行为时序”的进化

早期Moq版本的Verify()只能检查调用次数,比如mock.Verify(x => x.Save(), Times.Once())。这导致一个经典陷阱:当测试中多次调用Save()但只关心最后一次结果时,Times.Once()会误报失败。Moq 4.10+引入了VerifyAll()和更精细的时序控制:

// 验证Save被调用,且最后一次调用的参数是order mock.Verify(x => x.Save(It.Is<Order>(o => o.Status == OrderStatus.Completed)), Times.Exactly(1), "Save must be called once with completed order"); // 验证调用顺序:先Validate再Process var calls = mock.Invocations; Assert.IsTrue(calls[0].MethodName == "Validate" && calls[1].MethodName == "Process");

mock.Invocations返回的是IInvocation列表,每个元素包含方法名、参数、返回值、调用时间戳。这才是真正的行为审计——你不再问“有没有调用”,而是问“以什么顺序、什么参数、什么上下文调用”。这直接支撑了BDD风格的测试编写,比如用Given-When-Then结构组织测试:

// Given: 库存服务返回不足 inventoryMock.Setup(x => x.CheckStock("SKU-123")).Returns(false); // When: 创建订单 var result = orderService.Create(new OrderRequest { Sku = "SKU-123" }); // Then: 订单状态为PendingReview,且未调用支付服务 Assert.AreEqual(OrderStatus.PendingReview, result.Status); paymentMock.Verify(x => x.Charge(It.IsAny<Order>()), Times.Never());

3. 构建高效测试套件的四大实操支柱

3.1 支柱一:测试隔离的黄金三角——Arrange-Act-Assert的Moq化重构

传统AAA模式在Moq场景下需要重新定义:

  • Arrange(准备):不是“创建Mock对象”,而是“声明契约”。重点不是对象数量,而是契约精度。
  • Act(执行):保持极简,只调用被测方法,不插入任何中间状态操作。
  • Assert(断言):混合使用状态断言(Assert.AreEqual)和行为断言(Verify),但优先行为断言。

以电商优惠券服务为例,原始写法常犯的错误:

// ❌ 反模式:过度Arrange,模糊焦点 var couponMock = new Mock<ICouponService>(); couponMock.Setup(x => x.GetDiscount("SUMMER2023")).Returns(0.2m); couponMock.Setup(x => x.IsValid("SUMMER2023")).Returns(true); couponMock.Setup(x => x.GetExpiryDate("SUMMER2023")).Returns(DateTime.Now.AddDays(7)); // ... 还有5个Setup var service = new OrderCalculator(couponMock.Object); var result = service.CalculateTotal(new Order { Items = items }); // ❌ 反模式:Assert只验结果,不验行为 Assert.AreEqual(800m, result.Total); // 对,但没说清为什么是800

问题在于:Setup堆砌掩盖了业务重点,而Assert只验证数字结果,无法证明优惠券逻辑被正确触发。重构后:

// ✅ 正确:Arrange聚焦核心契约 var couponMock = new Mock<ICouponService>(); // 只声明最关键的两个契约:有效性和折扣率 couponMock.Setup(x => x.IsValid("SUMMER2023")).Returns(true); couponMock.Setup(x => x.GetDiscount("SUMMER2023")).Returns(0.2m); var service = new OrderCalculator(couponMock.Object); var result = service.CalculateTotal(new Order { Items = items }); // ✅ 正确:Assert分层验证 Assert.AreEqual(800m, result.Total); // 状态断言:结果正确 couponMock.Verify(x => x.IsValid("SUMMER2023"), Times.Once()); // 行为断言:确实检查了有效性 couponMock.Verify(x => x.GetDiscount("SUMMER2023"), Times.Once()); // 行为断言:确实获取了折扣

这个重构把测试用例从“验证计算结果”升级为“验证业务流程”。当未来需求变更(比如增加优惠券使用次数限制),你只需在Arrange中添加Setup(x => x.GetUsageCount("SUMMER2023")).Returns(3),并补充对应的Verify,而不用重写整个测试逻辑。

3.2 支柱二:依赖注入容器的测试友好改造——从“new Service()”到“ServiceProvider”

很多.NET项目用ASP.NET Core内置DI容器,但测试时仍习惯new OrderService(new PaymentService(), new InventoryService())。这导致两个问题:一是Mock对象无法注入到深层依赖,二是每次测试都要手动构造长依赖链。

正确做法是复用生产环境的DI注册逻辑,在测试中构建轻量IServiceProvider:

// 在测试基类中 protected IServiceProvider CreateTestServiceProvider() { var services = new ServiceCollection(); // 注册被测服务(用真实实现) services.AddTransient<OrderService>(); // 注册依赖(用Mock替代) var paymentMock = new Mock<IPaymentService>(); services.AddSingleton<IPaymentService>(sp => paymentMock.Object); var inventoryMock = new Mock<IInventoryService>(); services.AddSingleton<IInventoryService>(sp => inventoryMock.Object); return services.BuildServiceProvider(); } // 在测试方法中 [Test] public void CreateOrder_WithValidCoupon_ShouldApplyDiscount() { var provider = CreateTestServiceProvider(); var service = provider.GetRequiredService<OrderService>(); var paymentMock = provider.GetRequiredService<Mock<IPaymentService>>(); // Arrange paymentMock.Setup(x => x.Charge(It.IsAny<Order>())).Returns(true); // Act var result = service.Create(new OrderRequest { CouponCode = "SUMMER2023" }); // Assert Assert.IsTrue(result.Success); paymentMock.Verify(x => x.Charge(It.IsAny<Order>()), Times.Once()); }

这个模式的关键优势:

  1. 零侵入生产代码:不需要为测试添加任何#if DEBUG条件编译;
  2. 依赖关系自动解析:即使OrderService依赖10层深的服务,DI容器自动注入;
  3. Mock生命周期可控:Mock<T>实例由容器管理,避免多个测试间状态污染。

注意:不要在ServiceCollection中注册Mock<T>本身(如services.AddSingleton<Mock<IPaymentService>>()),而应注册Mock<T>.Object。否则provider.GetService<Mock<IPaymentService>>()会返回新实例,与Arrange阶段的Mock不是同一个对象。

3.3 支柱三:高级匹配与回调——处理复杂参数和副作用的实战技巧

现实业务中,参数往往是复杂对象,返回值依赖运行时状态。Moq提供It.Is<T>和Callback应对:

场景1:验证传入对象的特定属性
订单服务调用库存服务时,传入的是StockRequest对象,我们只关心Sku和Quantity:

var inventoryMock = new Mock<IInventoryService>(); inventoryMock.Setup(x => x.ReserveStock( It.Is<StockRequest>(r => r.Sku == "SKU-123" && r.Quantity == 2))) .Returns(true); // 或者更灵活:用Callback捕获完整对象做深度验证 StockRequest capturedRequest = null; inventoryMock.Setup(x => x.ReserveStock(It.IsAny<StockRequest>())) .Callback<StockRequest>(r => capturedRequest = r) .Returns(true); // Act service.ProcessOrder(order); // Assert Assert.IsNotNull(capturedRequest); Assert.AreEqual("SKU-123", capturedRequest.Sku); Assert.AreEqual(2, capturedRequest.Quantity);

场景2:模拟异步操作与延迟
支付服务通常有Task<bool> ChargeAsync(Order order)方法。Moq支持ReturnsAsync:

paymentMock.Setup(x => x.ChargeAsync(It.IsAny<Order>())) .ReturnsAsync(true) // 立即返回成功 .Verifiable(); // 标记为必须验证 // 模拟失败场景 paymentMock.Setup(x => x.ChargeAsync(It.IsAny<Order>())) .ReturnsAsync(false); // 模拟网络延迟 paymentMock.Setup(x => x.ChargeAsync(It.IsAny<Order>())) .ReturnsAsync(true) .Callback(() => Thread.Sleep(100)); // 同步延迟(仅测试用)

场景3:验证方法调用顺序与并发安全
库存扣减需保证原子性,测试需验证是否加锁:

var lockMock = new Mock<ILockService>(); lockMock.Setup(x => x.AcquireLock("SKU-123")) .Returns(true) .Callback(() => Console.WriteLine("Lock acquired")); lockMock.Setup(x => x.ReleaseLock("SKU-123")) .Callback(() => Console.WriteLine("Lock released")); // Arrange var service = new InventoryService(lockMock.Object, inventoryMock.Object); // Act service.DecreaseStock("SKU-123", 1); // Assert:验证调用顺序 var invocations = lockMock.Invocations; Assert.AreEqual("AcquireLock", invocations[0].MethodName); Assert.AreEqual("ReleaseLock", invocations[1].MethodName);

3.4 支柱四:测试数据工厂与可组合Setup——告别重复代码

每个测试方法里写Setup(x => x.GetProduct("P1")).Returns(...)会导致大量重复。解决方案是测试数据工厂:

public static class TestProductFactory { public static Mock<IProductService> WithProduct(string sku, string name, decimal price) { var mock = new Mock<IProductService>(); mock.Setup(x => x.GetProduct(sku)) .Returns(new Product { Sku = sku, Name = name, Price = price }); return mock; } public static Mock<IProductService> WithNotFoundProduct(string sku) { var mock = new Mock<IProductService>(); mock.Setup(x => x.GetProduct(sku)) .Throws(new ProductNotFoundException($"Product {sku} not found")); return mock; } } // 在测试中复用 [Test] public void CalculateTotal_WithValidProduct_ShouldUsePrice() { var productMock = TestProductFactory.WithProduct("P1", "Laptop", 1000m); var service = new OrderCalculator(productMock.Object); var result = service.CalculateTotal(new Order { Items = new[] { "P1" } }); Assert.AreEqual(1000m, result.Total); } [Test] public void CalculateTotal_WithInvalidProduct_ShouldThrow() { var productMock = TestProductFactory.WithNotFoundProduct("P999"); var service = new OrderCalculator(productMock.Object); Assert.Throws<ProductNotFoundException>(() => service.CalculateTotal(new Order { Items = new[] { "P999" } })); }

更进一步,用可组合Setup处理多依赖场景:

public static class TestServiceFactory { public static (Mock<IProductService>, Mock<IInventoryService>, Mock<IPaymentService>) CreateStandardMocks() { var productMock = new Mock<IProductService>(); productMock.Setup(x => x.GetProduct("P1")).Returns(new Product { Price = 100m }); var inventoryMock = new Mock<IInventoryService>(); inventoryMock.Setup(x => x.CheckStock("P1")).Returns(true); var paymentMock = new Mock<IPaymentService>(); paymentMock.Setup(x => x.Charge(It.IsAny<Order>())).Returns(true); return (productMock, inventoryMock, paymentMock); } } // 测试中一键获取 var (productMock, inventoryMock, paymentMock) = TestServiceFactory.CreateStandardMocks(); var service = new OrderService(productMock.Object, inventoryMock.Object, paymentMock.Object);

4. 常见问题与避坑指南:那些文档不会写的血泪教训

4.1 问题速查表:高频报错与根因分析

错误信息根本原因解决方案我的实操心得
System.ArgumentException: Expression is not a method invocation在Setup中用了属性访问(如x.Name)而非方法调用改用SetupGet(x => x.Name)或SetupSet(x => x.Name)属性和方法在Moq中是不同契约,混用必报错。宁可多写一行SetupGet,也不要尝试x => x.Name()这种非法语法
Moq.MockException: All invocations on the mock must have a corresponding setup调用了未Setup的方法,且Mock是Strict模式方案1:删掉MockBehavior.Strict;方案2:为所有可能调用的方法添加SetupStrict模式适合契约驱动开发,但初期会很痛苦。建议新项目从Loose开始,等契约稳定后再切Strict
System.NullReferenceExceptionin VerifyVerify时Mock对象已被GC回收,或Verify调用在Assert之后确保Verify在Assert之前执行;检查Mock是否在using块中被释放我踩过最深的坑:在一个[TestCleanup]里mock.Reset(),结果Verify失效。记住:Reset会清空所有Setup和调用记录
The invocation was not performed on the mockVerify时参数不匹配(如字符串大小写、浮点数精度)用It.Is<T>放宽匹配条件;对decimal用It.Is<decimal>(d => Math.Abs(d - 99.9m) < 0.01m)浮点数比较永远用误差范围,这是数学常识,但在Mock里容易忽略

4.2 高级避坑:密封类、静态方法与内部成员的应对策略

Moq只能Mock接口和虚方法,遇到密封类(如HttpClient)、静态方法(如DateTime.Now)、内部成员(internal class Logger)怎么办?

密封类处理:
HttpClient是典型密封类,不能直接Mock。正确姿势是依赖抽象:

// 定义接口(生产代码中已有) public interface IHttpService { Task<T> GetAsync<T>(string url); } // 生产实现 public class HttpService : IHttpService { private readonly HttpClient _client; public HttpService(HttpClient client) => _client = client; public async Task<T> GetAsync<T>(string url) => await JsonSerializer.DeserializeAsync<T>(await _client.GetStreamAsync(url)); } // 测试中Mock接口 var httpMock = new Mock<IHttpService>(); httpMock.Setup(x => x.GetAsync<Product>("https://api/products/1")) .ReturnsAsync(new Product { Id = 1, Name = "Laptop" });

静态方法处理:
DateTime.Now无法Mock,但可以封装:

public interface IClock { DateTime Now { get; } } public class SystemClock : IClock { public DateTime Now => DateTime.Now; } // 测试中 var clockMock = new Mock<IClock>(); clockMock.Setup(x => x.Now).Returns(new DateTime(2023, 1, 1));

内部成员处理:
在AssemblyInfo.cs中添加:

// 允许测试项目访问内部成员 [assembly: InternalsVisibleTo("MyApp.Tests")]

然后Mock内部类:

var loggerMock = new Mock<InternalLogger>(); // InternalLogger是internal class loggerMock.Setup(x => x.Log("Order created")).Verifiable();

4.3 性能陷阱:为什么你的测试越来越慢

Moq本身很快,但不当用法会让测试变慢:

  • 陷阱1:在循环中创建Mock

    // ❌ 每次循环都新建Mock,生成代理类开销大 foreach (var item in items) { var mock = new Mock<IProductService>(); mock.Setup(x => x.GetPrice(item.Sku)).Returns(item.Price); // ... }

    ✅ 正确:循环外创建,Setup支持多次调用

    var mock = new Mock<IProductService>(); foreach (var item in items) { mock.Setup(x => x.GetPrice(item.Sku)).Returns(item.Price); }
  • 陷阱2:Setup中执行耗时操作

    // ❌ Setup时就调用数据库查询 mock.Setup(x => x.GetConfig()).Returns(() => LoadFromDatabase());

    ✅ 正确:Setup只声明,执行在Act阶段

    mock.Setup(x => x.GetConfig()).Returns(new Config { Timeout = 30 });
  • 陷阱3:VerifyAll()滥用
    VerifyAll()会检查所有Setup是否被调用,但很多Setup是“可选契约”。当测试用例只覆盖部分路径时,VerifyAll()必然失败。我的经验是:只对核心契约用Verify,非核心用Verifiable()标记,然后选择性Verify。

4.4 架构级避坑:测试套件与CI/CD的协同设计

一个高效测试套件必须考虑CI/CD流水线:

  • 分层执行:在CI中分离unit(Moq为主)、integration(真实DB/API)、e2e(浏览器)测试。用[TestCategory("Unit")]标记,CI脚本按类别执行:

    dotnet test --filter "TestCategory=Unit" --logger trx
  • 并行化安全:Moq默认线程安全,但Mock<T>.Object是线程安全的,Mock<T>实例不是。所以:

    // ❌ 危险:多个测试共享同一Mock实例 private static readonly Mock<IService> SharedMock = new Mock<IService>(); // ✅ 安全:每个测试用独立Mock [Test] public void Test1() { var mock = new Mock<IService>(); // ... }
  • 覆盖率报告:用coverlet生成OpenCover格式报告,集成到Azure DevOps或GitHub Actions:

    <!-- csproj中 --> <PackageReference Include="coverlet.msbuild" Version="3.2.0" />
    dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover

5. 从Moq到测试文化:一个资深开发者的真实体会

我在三个不同规模的.NET项目中推行Moq,最大的教训不是技术问题,而是认知偏差。第一个项目,团队把Moq当“测试必需品”,但没人理解Setup的本质是契约声明。结果测试代码里充斥着Setup(x => x.DoSomething()).Returns(true),而DoSomething方法根本没业务含义——它只是为Mock而存在的空壳。测试通过了,但业务逻辑漏洞百出。

第二个项目,我们走了另一个极端:用Moq模拟一切,包括DateTime.Now、Guid.NewGuid()、甚至ConfigurationManager.AppSettings。测试运行飞快,但当部署到生产环境,发现时区配置错误导致定时任务全部失效。问题不在Moq,而在我们忘了测试的终极目标不是“代码能跑”,而是“业务能稳”。

第三个项目的突破点,是把Moq纳入需求评审环节。产品经理提需求:“优惠券过期前1小时发短信提醒”,开发立刻写出测试骨架:

// Given: 优惠券明天过期 couponMock.Setup(x => x.GetExpiryDate("SUMMER2023")).Returns(DateTime.Now.AddDays(1)); // When: 执行提醒任务 reminderService.Run(); // Then: 发送短信 smsMock.Verify(x => x.Send(It.Is<SmsMessage>(m => m.Content.Contains("1小时"))), Times.Once());

这个测试在编码前就存在,它迫使所有人思考:什么是“过期前1小时”?是UTC时间还是本地时间?短信内容模板谁来确认?这种前置契约,让Moq从测试工具升级为需求对齐工具。

所以,Moq的“高效”二字,最终指向的不是执行速度,而是反馈速度——从提交代码到确认业务正确的时长。我现在的标准是:一个新功能的单元测试,应该能在3分钟内写完、10秒内跑完、且让任何同事看懂它在保护什么业务规则。如果做不到,不是Moq不够好,是我们还没找到那个最精准的契约点。

最后分享一个小技巧:在大型解决方案中,为每个领域建立TestHelpers项目,里面放MockExtensions类:

public static class MockExtensions { public static void SetupAsSuccess<T>(this Mock<T> mock, string methodName) where T : class { var method = typeof(T).GetMethod(methodName); if (method.ReturnType == typeof(Task)) { mock.As<ITransient>().Setup(x => x.GetType().GetMethod(methodName)).Returns(Task.CompletedTask); } else if (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)) { var returnType = method.ReturnType.GetGenericArguments()[0]; var taskResult = Activator.CreateInstance(returnType); mock.As<ITransient>().Setup(x => x.GetType().GetMethod(methodName)) .Returns(Task.FromResult(taskResult)); } } }

这个扩展让mock.SetupAsSuccess("ChargeAsync")成为一行代码,省去重复的ReturnsAsync(true)。工具的价值,永远在于它如何放大人的判断力,而不是替代人的思考。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询