如何在AutoFixture中避免全局自定义,实现特定类型专属配置
问题场景
我尝试通过ISpecimenBuilder自定义AutoFixture的对象创建逻辑:
var fixture= new Fixture(); fixture.Customizations.Add(new SkipNonPrimitiveMembersCustomization()); var person= fixture.Create<Person>(); // 符合预期 var node = fixture.Create<Node>(); // 仅设置了基本类型属性
全局配置下Node对象的创建结果不符合预期,因此我尝试用FromFactory只为Node应用该自定义:
var fixture = new Fixture(); var node = fixture.Build<Node>() .FromFactory(new SkipNonPrimitiveMembersCustomization()) .Create();
执行后抛出异常:
消息: AutoFixture.ObjectCreationException : 被装饰的ISpecimenBuilder无法基于请求创建样本:SkipNonPrimitiveMembersCustomizationTests+Node。这种情况可能发生在请求代表接口或抽象类时;若是如此,请注册一个可基于该请求创建样本的ISpecimenBuilder。如果在强类型Build表达式中出现此问题,请尝试使用IFactoryComposer方法之一提供工厂。
堆栈跟踪:
NoSpecimenOutputGuard.Create(Object request, ISpecimenContext context)
Postprocessor1.Create(Object request, ISpecimenContext context) CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context) FilteringSpecimenBuilder.Create(Object request, ISpecimenContext context) NodeComposer1.Create(Object request, ISpecimenContext context)
CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context)
RecursionGuard.Create(Object request, ISpecimenContext context)
CompositeNodeComposer1.Create(Object request, ISpecimenContext context) SpecimenContext.Resolve(Object request) SeedIgnoringRelay.Create(Object request, ISpecimenContext context) CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context) FilteringSpecimenBuilder.Create(Object request, ISpecimenContext context) NodeComposer1.Create(Object request, ISpecimenContext context)
CompositeSpecimenBuilder.Create(Object request, ISpecimenContext context)
RecursionGuard.Create(Object request, ISpecimenContext context)
CompositeNodeComposer1.Create(Object request, ISpecimenContext context) SpecimenContext.Resolve(Object request) SpecimenFactory.Create[T](ISpecimenContext context) SpecimenFactory.Create[T](ISpecimenBuilder builder) SpecimenFactory.Create[T](IPostprocessComposer1 composer)
我的ISpecimenBuilder实现及Node定义如下:
public class SkipNonPrimitiveMembersCustomization : ISpecimenBuilder { public object Create(object request, ISpecimenContext context) { if (request is PropertyInfo propInfo && propInfo != null && !IsPrimitiveType(propInfo.PropertyType)) return new OmitSpecimen(); return new NoSpecimen(); } private static bool IsPrimitiveType(Type type) { if (type is null) return false; return type.IsPrimitive || type.IsValueType || type == typeof(string) || // 处理基本类型数组 type.IsArray && IsPrimitiveType(type.GetElementType()) == true || type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>) && type.GenericTypeArguments.All(IsPrimitiveType) || // 处理泛型参数均为基本值类型的泛型类型 type.GetGenericArguments().Any(t => t.IsValueType && IsPrimitiveType(t)); } } private class Node { public string Name { get; set; } public int Id { get; set; } public string[] Tags { get; set; } public TestEnum? TestEnum { get; set; } public DateTime? CreatedAt { get; set; } public Node[] Children { get; set; } }
另外我已配置循环引用处理:
fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList() .ForEach(b => fixture.Behaviors.Remove(b)); fixture.Behaviors.Add(new OmitOnRecursionBehavior());
问题原因
FromFactory要求传入的是能直接生成目标类型实例的工厂(如Func<Node>、IInstanceFactory<Node>),但你的SkipNonPrimitiveMembersCustomization仅负责处理属性级别的创建请求,无法直接生成完整的Node实例,因此触发异常。
解决方案
方式1:针对Node单独注册自定义逻辑
通过fixture.Customize<Node>为该类型单独应用自定义,无需全局配置:
var fixture = new Fixture(); // 只为Node类型应用自定义规则 fixture.Customize<Node>(composer => { var builder = new CompositeSpecimenBuilder( new SkipNonPrimitiveMembersCustomization(), fixture.Resolve<ISpecimenBuilder>() ); return new SpecimenContext(builder).Create<Node>(); }); var node = fixture.Create<Node>();
方式2:修改Builder支持类型实例创建
调整SkipNonPrimitiveMembersCustomization,使其能处理类型创建请求,先委托上下文生成实例,再过滤非基本类型属性:
public class SkipNonPrimitiveMembersCustomization : ISpecimenBuilder { public object Create(object request, ISpecimenContext context) { // 处理类型创建请求:先生成实例,再清理非基本类型属性 if (request is Type type) { var instance = context.Create(type); foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { if (!IsPrimitiveType(prop.PropertyType)) { prop.SetValue(instance, null); } } return instance; } // 处理属性请求:跳过非基本类型属性 if (request is PropertyInfo propInfo && propInfo != null && !IsPrimitiveType(propInfo.PropertyType)) return new OmitSpecimen(); return new NoSpecimen(); } private static bool IsPrimitiveType(Type type) { if (type is null) return false; return type.IsPrimitive || type.IsValueType || type == typeof(string) || type.IsArray && IsPrimitiveType(type.GetElementType()) || type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>) && type.GenericTypeArguments.All(IsPrimitiveType) || // 修正原逻辑的Any为All,避免误判包含非基本类型参数的泛型 type.GetGenericArguments().All(t => t.IsValueType && IsPrimitiveType(t)); } }
使用时通过上下文传入调用:
var fixture = new Fixture(); var node = fixture.Build<Node>() .FromFactory(ctx => new SkipNonPrimitiveMembersCustomization().Create(typeof(Node), ctx)) .Create();
方式3:直接用AutoFixture内置API控制属性
如果仅需跳过Node的特定非基本类型属性,直接使用Build<T>.Without更简洁:
var fixture = new Fixture(); var node = fixture.Build<Node>() .Without(x => x.Children) .Create();
内容的提问来源于stack exchange,提问作者Daniel B

