.NET Core单元测试:如何执行Expression<Action<T>>传入的方法?
解决方法
你的Stub无法正常调用目标方法的核心原因是:Expression<Action<T>>描述的是必须依赖T类型实例才能执行的实例方法,而你之前的代码没有提供对应的T实例就直接尝试调用,导致执行失败。
修正后的Stub实现
方案一:基于依赖注入获取实例(推荐用于常规单元测试场景)
如果你的单元测试使用了依赖注入框架(比如结合xUnit使用Microsoft.Extensions.DependencyInjection),可以让Stub通过IServiceProvider获取目标服务实例:
public class BackgroundTasksServiceStub : IBackgroundTasksService { private readonly IServiceProvider _serviceProvider; public BackgroundTasksServiceStub(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public void RunBackgroundTask<T>(Expression<Action<T>> methodCall) { // 从DI容器中获取T类型的服务实例 var serviceInstance = _serviceProvider.GetRequiredService<T>(); // 编译表达式为可执行委托,传入实例执行方法 var executeAction = methodCall.Compile(); executeAction(serviceInstance); } }
方案二:手动指定实例(适用于简单测试场景)
如果测试场景不需要完整的DI容器,也可以在Stub中添加属性手动传入目标实例:
public class BackgroundTasksServiceStub : IBackgroundTasksService { // 用于手动设置要调用的服务实例 public object? TargetInstance { get; set; } public void RunBackgroundTask<T>(Expression<Action<T>> methodCall) { if (TargetInstance is not T serviceInstance) { throw new InvalidOperationException($"请设置类型为{typeof(T).FullName}的实例"); } var executeAction = methodCall.Compile(); executeAction(serviceInstance); } }
测试使用示例
以DI方案为例,测试时的代码示例:
// 构建测试用的服务容器 var services = new ServiceCollection(); services.AddScoped<MyService>(); // 注册你的业务服务 services.AddScoped<IBackgroundTasksService>(sp => new BackgroundTasksServiceStub(sp)); using var serviceProvider = services.BuildServiceProvider(); // 获取后台任务服务并调用 var backgroundTaskService = serviceProvider.GetRequiredService<IBackgroundTasksService>(); backgroundTaskService.RunBackgroundTask<MyService>(x => x.Execute(1, "test_item", "blue")); // 此时会直接同步执行MyService的Execute方法
原代码失败的原因
你之前的代码Expression.Lambda(methodCall).Compile().DynamicInvoke();存在两个关键问题:
methodCall本身已经是Expression<Action<T>>类型,无需再用Expression.Lambda包装,直接调用Compile()即可得到Action<T>委托。Action<T>要求传入一个T类型的参数(即实例对象),但你调用DynamicInvoke()时没有传递任何参数,因此无法执行实例方法。
内容的提问来源于stack exchange,提问作者The Fabio
相关产品推荐
相关产品推荐

