如何在ASP.NET Razor Page中对Validator验证器执行验证测试
this.factory的作用
你代码里的WebApplicationFactory<Startup>是ASP.NET Core官方提供的集成测试工具类,结合XUnit的IClassFixture特性,会在当前测试类的所有用例执行前,一次性启动一个和线上运行环境配置一致的内存级Web应用宿主,这个factory就是该测试宿主的实例,它完整保留了你项目中Startup/Program类里的所有服务注册、中间件配置、路由规则,无需你手动搭建测试依赖环境,也不需要真实部署Web服务就能执行测试逻辑。
factory.Services.GetService()的功能
factory.Services是测试宿主对应的依赖注入(DI)容器根实例,GetService<T>()是DI容器的标准服务获取方法:你传入要获取的服务类型作为泛型参数,方法会在容器的服务注册列表中查找该类型对应的实现类,如果找到了就返回实例,没找到则返回null。如果你确定服务必然已经注册,可以替换为GetRequiredService<T>(),该方法在找不到服务时会直接抛出异常,更便于定位注册问题。
注意:你当前的
FoodItemValidator存在逻辑缺陷:errors是类级别的字段,每次调用IsValid方法不会清空历史错误,多次调用同一个验证器实例时错误会累加,建议修改为方法内部的局部变量,修复后的代码如下:
public class FoodItemValidator : IFoodItemValidator { public string[] IsValid(FoodItem foodItem) { // 将errors移动到方法内部,每次调用重新初始化 List<string> errors = new List<string>(); if (string.IsNullOrWhiteSpace(foodItem.Name)) { errors.Add("Name should contain a value and not a space."); } if (string.IsNullOrWhiteSpace(foodItem.Description)) { errors.Add("Description should contain a value and not a space."); } if (foodItem.Price <= 0) { errors.Add("Price should be greater than 0."); } return errors.ToArray(); } }
另外需要确认你项目的Startup/Program类中已经注册了验证器服务:services.AddScoped<IFoodItemValidator, FoodItemValidator>();
以下是覆盖所有校验场景的测试代码,基于你现有的WebApplicationFactory框架编写:
using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using Xunit; using Food.Project; namespace Food.Project.Tests { public class WebApplicationTests : IClassFixture<WebApplicationFactory<Startup>> { private readonly WebApplicationFactory<Startup> _factory; private readonly IFoodItemValidator _validator; public WebApplicationTests(WebApplicationFactory<Startup> factory) { _factory = factory; // 测试类初始化时直接获取验证器实例 _validator = _factory.Services.GetRequiredService<IFoodItemValidator>(); } [Fact] public void Validator_AllFieldsValid_ReturnsEmptyErrorArray() { // 构造完全合法的FoodItem var validItem = new FoodItem { Name = "汉堡", Description = "经典牛肉汉堡", Price = 29.9 }; var errors = _validator.IsValid(validItem); Assert.Empty(errors); } [Fact] public void Validator_NameIsEmpty_ReturnsNameError() { var invalidItem = new FoodItem { Name = "", Description = "经典牛肉汉堡", Price = 29.9 }; var errors = _validator.IsValid(invalidItem); Assert.Single(errors); Assert.Contains("Name should contain a value and not a space.", errors); } [Fact] public void Validator_DescriptionIsWhitespace_ReturnsDescriptionError() { var invalidItem = new FoodItem { Name = "汉堡", Description = " ", Price = 29.9 }; var errors = _validator.IsValid(invalidItem); Assert.Single(errors); Assert.Contains("Description should contain a value and not a space.", errors); } [Fact] public void Validator_PriceIsZero_ReturnsPriceError() { var invalidItem = new FoodItem { Name = "汉堡", Description = "经典牛肉汉堡", Price = 0 }; var errors = _validator.IsValid(invalidItem); Assert.Single(errors); Assert.Contains("Price should be greater than 0.", errors); } [Fact] public void Validator_MultipleFieldsInvalid_ReturnsAllRelatedErrors() { var invalidItem = new FoodItem { Name = "", Description = "", Price = -10 }; var errors = _validator.IsValid(invalidItem); Assert.Equal(3, errors.Length); Assert.Contains("Name should contain a value and not a space.", errors); Assert.Contains("Description should contain a value and not a space.", errors); Assert.Contains("Price should be greater than 0.", errors); } } }
内容的提问来源于stack exchange,提问作者Maria Hussey

