如何使用AutoFixture创建含内部构造函数及自引用参数的类型
解决AutoFixture创建带内部构造函数且含自引用参数类型实例的问题
问题描述
使用AutoFixture.AutoMoq 4.17.0版本,尝试创建带有内部构造函数且构造函数包含自引用参数的TypeWithInternalConstructor实例时,抛出以下异常:
AutoFixture.ObjectCreationExceptionWithPath: AutoFixture无法创建XXX.TypeWithInternalConstructor的实例,很可能因为它没有公共构造函数,或是抽象类型或非公开类型。
调试发现,解析构造函数的child参数时得到OmitSpecimen(因递归触发OmitOnRecursionBehavior),但该特殊标本无法被构造函数的参数类型接受,导致创建失败。
解决方案
核心思路是拦截递归产生的OmitSpecimen,将其替换为null后再传入内部构造函数,同时保留原有的内部构造函数解析逻辑。具体实现如下:
1. 自定义标本命令处理OmitSpecimen转换
创建一个ISpecimenCommand实现类,用于将构造函数参数中的OmitSpecimen替换为null:
public class OmitSpecimenToNullCommand : ISpecimenCommand { private readonly ISpecimenCommand _innerCommand; public OmitSpecimenToNullCommand(ISpecimenCommand innerCommand) { _innerCommand = innerCommand ?? throw new ArgumentNullException(nameof(innerCommand)); } public void Execute(object specimen, ISpecimenContext context) { if (specimen is MethodInvokerContext invocationContext) { // 将所有OmitSpecimen类型的参数替换为null invocationContext.Parameters = invocationContext.Parameters .Select(p => p is OmitSpecimen ? null : p) .ToArray(); } _innerCommand.Execute(specimen, context); } }
2. 修改Fixture配置,整合自定义命令
在原有Fixture配置基础上,用自定义命令包装AutoPropertiesCommand,确保参数替换逻辑生效:
[TestClass] public class AutoFixtureExperiments { [TestMethod] public void ShouldActivateTypesWithInternalConstructor() { var fixture = new Fixture(); // 替换递归行为:超过指定深度时返回OmitSpecimen fixture.Behaviors.Remove(fixture.Behaviors.OfType<ThrowingRecursionBehavior>().First()); fixture.Behaviors.Add(new OmitOnRecursionBehavior(1)); // 添加自定义解析器:处理内部构造函数+转换OmitSpecimen为null fixture.ResidueCollectors.Add( new Postprocessor( new MethodInvoker(new ModestInternalConstructorQuery()), new OmitSpecimenToNullCommand(new AutoPropertiesCommand()) )); var result = fixture.Create<TypeWithInternalConstructor>(); Assert.AreNotEqual(0, result.Property); Assert.IsNull(result.Child); // 递归参数被替换为null } } // 保留原有的ModestInternalConstructorQuery实现 public class ModestInternalConstructorQuery : IMethodQuery { public IEnumerable<IMethod> SelectMethods(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); return from ci in type.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic) where ci.IsAssembly // 仅选择内部构造函数 let parameters = ci.GetParameters() orderby parameters.Length ascending select new ConstructorMethod(ci) as IMethod; } } public class TypeWithInternalConstructor { public int Property { get; } public TypeWithInternalConstructor Child { get; } internal TypeWithInternalConstructor(int property, TypeWithInternalConstructor child) { Property = property; Child = child; } }
关键说明
OmitOnRecursionBehavior(1)设置递归深度为1:第一次创建TypeWithInternalConstructor时,其child参数触发递归,直接返回OmitSpecimen。OmitSpecimenToNullCommand将OmitSpecimen转换为null,确保内部构造函数能正常接收参数完成实例化。- 保留
ModestInternalConstructorQuery确保AutoFixture优先选择内部构造函数。
内容的提问来源于stack exchange,提问作者donttellya
相关产品推荐
相关产品推荐

