如何自定义AutoFixture的Verify行为验证空IEnumerable返回结果?
自定义AutoFixture GuardClauseAssertion验证返回空IEnumerable
默认的GuardClauseAssertion确实是为了验证方法在参数为null时抛出ArgumentNullException设计的,不过要改成验证返回空IEnumerable<T>也不难——我们只需要替换它的核心断言逻辑就行。下面给你两种可行的方案:
方案一:手动遍历参数+AutoFixture生成有效值(简单直接)
如果你的场景不复杂,直接手动遍历每个参数,将其设为null,用AutoFixture生成其他参数的有效值,然后验证返回结果为空即可。这种方式不需要自定义太多类,上手快:
var fixture = new Fixture(); // 获取泛型方法的具体MethodInfo(这里以int类型为例,替换成你的实际类型) var myMethodInfo = typeof(YourClassName) .GetMethod(nameof(YourClassName.MyMethod)) .MakeGenericMethod(typeof(int)); // 如果是实例方法,创建类实例;静态方法则不需要 var instance = fixture.Create<YourClassName>(); // 遍历每个参数,逐个设为null进行验证 foreach (var param in myMethodInfo.GetParameters()) { // 生成参数数组:当前参数设为null,其他参数用AutoFixture生成合法值 var parameters = myMethodInfo.GetParameters() .Select(p => p == param ? null : fixture.Create(p.ParameterType)) .ToArray(); // 调用方法并转换结果类型 var result = (IEnumerable<int>)myMethodInfo.Invoke(instance, parameters); // 验证结果是空枚举 Assert.Empty(result); }
方案二:自定义GuardClauseCommand(贴合AutoFixture风格)
如果你想保持和GuardClauseAssertion类似的调用风格,可以自定义一个实现IGuardClauseCommand的类,替换默认的异常检查逻辑为返回值验证,再封装成扩展方法:
第一步:自定义GuardClauseCommand
这个类负责定义当参数为null时的验证逻辑:
public class ReturnEmptyEnumerableGuardClauseCommand : IGuardClauseCommand { private readonly MethodInfo _targetMethod; private readonly object _instance; // 实例方法的实例,静态方法为null public ReturnEmptyEnumerableGuardClauseCommand(MethodInfo targetMethod, object instance = null) { _targetMethod = targetMethod ?? throw new ArgumentNullException(nameof(targetMethod)); _instance = instance; } public void Execute(object[] parameters, int parameterIndex) { // 将指定索引的参数设为null parameters[parameterIndex] = null; // 调用目标方法 var result = _targetMethod.Invoke(_instance, parameters); // 验证返回的是IEnumerable且为空 if (result is not IEnumerable enumerable || enumerable.Cast<object>().Any()) { var paramName = _targetMethod.GetParameters()[parameterIndex].Name; throw new AssertionException( $"When parameter '{paramName}' is null, expected method to return empty IEnumerable, but got non-empty result."); } } public string Request => _targetMethod.Name; public string Specimen => string.Join(", ", _targetMethod.GetParameters().Select(p => p.Name)); }
第二步:封装扩展方法
给GuardClauseAssertion添加扩展方法,方便调用:
public static class GuardClauseAssertionExtensions { public static void VerifyReturnsEmptyEnumerableOnNullParameters( this GuardClauseAssertion assertion, MethodInfo methodInfo, object instance = null) { var command = new ReturnEmptyEnumerableGuardClauseCommand(methodInfo, instance); var specimenContext = new SpecimenContext(assertion.Fixture); // 遍历每个参数,执行自定义验证逻辑 foreach (var parameter in methodInfo.GetParameters()) { var paramIndex = Array.IndexOf(methodInfo.GetParameters(), parameter); // 生成参数数组:当前参数为null,其他用AutoFixture生成 var parameters = methodInfo.GetParameters() .Select(p => p == parameter ? null : specimenContext.Resolve(p.ParameterType)) .ToArray(); command.Execute(parameters, paramIndex); } } }
第三步:使用扩展方法验证
调用方式和原Verify方法几乎一致:
var fixture = new Fixture(); var assertion = new GuardClauseAssertion(fixture); var myMethodInfo = typeof(YourClassName) .GetMethod(nameof(YourClassName.MyMethod)) .MakeGenericMethod(typeof(YourType)); // 实例方法传入实例,静态方法不需要第二个参数 var instance = fixture.Create<YourClassName>(); assertion.VerifyReturnsEmptyEnumerableOnNullParameters(myMethodInfo, instance);
两种方案各有优劣:手动方式适合快速实现,自定义命令的方式更符合AutoFixture的设计风格,也方便复用。你可以根据自己的场景选择~
内容的提问来源于stack exchange,提问作者g.pickardou
相关产品推荐
相关产品推荐

