为依赖IActionDescriptorCollectionProvider的TagHelper单元测试配置依赖
直接说结论:别费劲去实例化DefaultActionDescriptorCollectionProvider——它是ASP.NET Core的内部实现类,依赖一堆复杂的服务(比如IActionDescriptorProvider、IActionDescriptorChangeProvider),单元测试里不仅初始化成本极高,还没法直接修改它的ActionDescriptors.Items。最简便也最专业的方式就是用NSubstitute这类模拟框架来替代它。
具体实现步骤
构建自定义的ActionDescriptor集合
手动创建ControllerActionDescriptor实例,设置好和测试场景匹配的Area、Controller、Action等路由属性,再把它们包装成ActionDescriptorCollection。用NSubstitute生成模拟的依赖对象
让这个模拟的IActionDescriptorCollectionProvider返回你刚才构建的自定义集合,这样TagHelper就能拿到你预设的路由信息。注入模拟实例到TagHelper
把模拟的提供者和HttpContextAccessor一起传入TagHelper构造函数,就能正常开展测试了。
修改后的Setup代码示例
using NSubstitute; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Http; [SetUp] public void Setup() { // 初始化HttpContextAccessor var contextAccessor = new HttpContextAccessor(); contextAccessor.HttpContext = new DefaultHttpContext(); contextAccessor.HttpContext.Request.Path = "/foo"; // 构建模拟的ActionDescriptor集合 var testActionDescriptors = new List<ActionDescriptor> { new ControllerActionDescriptor { RouteValues = new Dictionary<string, string> { ["area"] = "", ["controller"] = "Home", ["action"] = "Index" } // 可根据测试需求添加更多属性,比如AttributeRouteInfo } }; var actionDescriptorCollection = new ActionDescriptorCollection(testActionDescriptors, version: 1); // 用NSubstitute模拟IActionDescriptorCollectionProvider var actionDescriptorProvider = Substitute.For<IActionDescriptorCollectionProvider>(); actionDescriptorProvider.ActionDescriptors.Returns(actionDescriptorCollection); // 初始化TagHelper var helper = new ActivePathSegmentTagHelper(contextAccessor, actionDescriptorProvider); helper.Area = ""; helper.Controller = "Home"; helper.Action = "Index"; }
如何动态修改ActionDescriptors.Items?
ActionDescriptorCollection的Items是只读的,但你可以在测试方法里重新构建新集合,然后让模拟对象返回这个新集合:
[Test] public void TestDifferentRouteScenario() { // 构建新的ActionDescriptor集合 var newDescriptors = new List<ActionDescriptor> { new ControllerActionDescriptor { RouteValues = new Dictionary<string, string> { ["area"] = "Admin", ["controller"] = "Dashboard", ["action"] = "Overview" } } }; var newCollection = new ActionDescriptorCollection(newDescriptors, version: 2); // 让模拟提供者返回新集合 actionDescriptorProvider.ActionDescriptors.Returns(newCollection); // 执行TagHelper逻辑并验证结果 // ... }
替代方案:手动实现测试用Provider
如果不想用模拟框架,也可以自己写一个极简的实现类:
public class TestActionDescriptorCollectionProvider : IActionDescriptorCollectionProvider { public ActionDescriptorCollection ActionDescriptors { get; set; } public TestActionDescriptorCollectionProvider(ActionDescriptorCollection collection) { ActionDescriptors = collection; } }
然后在Setup里替换成这个类的实例:
var actionDescriptorProvider = new TestActionDescriptorCollectionProvider(actionDescriptorCollection);
这种方式虽然可行,但灵活性不如模拟框架(比如没法验证调用次数、动态修改返回值),所以还是优先推荐NSubstitute。
内容的提问来源于stack exchange,提问作者Carl

