You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 02:57:32