【问题标题】:AutoFixture type customization not invoked未调用 AutoFixture 类型自定义
【发布时间】:2019-09-06 18:52:58
【问题描述】:

给定

这是我正在测试的类结构。

public class Company
{
    public List<Department> Departments { get; set; }
}

public class Department
{
    public List<Employee> Employees { get; set; }
}

public class Employee
{
    public string Name { get; set; }
    public DateTime HireDate { get; set; }
}

当 AutoFixture 被告知 Create&lt;Company&gt;() 时,我希望将 Employee 的自定义对象添加到 Department.Employees

我认为应该这样写:

_fixture.Customize<Department>(
            c => c.Do(d => d.Employees
                            .Add(_fixture.Build<Employee>()
                                         .With(e => e.Name, "simple name")
                                         .Create())));

_fixtrue.Create<Company>();

然而

当我运行测试时,Company.Departments 属性填充了 3 个对象,这三个对象中的每一个都有一个填充有 3 个对象(预期为 4 或 1)的 Department.Employees 列表,并且没有一个 Employee 对象有指定的名称。 为什么?

可能相关

我还在夹具上使用AutoNSubstituteCustomizationISpecimenBuilder 的本地实现来处理不相关的类型。

ISpecimenBuilder 是:

    public class PropertyTypeOmission 
    {
        public SelectionContext<TDeclaringType> For<TDeclaringType>()
        {
            return new SelectionContext<TDeclaringType>();
        }

        public class SelectionContext<TDeclaringType> 
        {
            public OmitMemberTypeByType Omitting<TPropertyType>()
            {
                return new OmitMemberTypeByType(typeof(TDeclaringType), typeof(TPropertyType));
            }

            public OmitMemberTypeByType Omitting<TMemberType>(Expression<Func<TDeclaringType, TMemberType>> select)
            {
                var memberExp = GetMemberExpression(select);
                var property = GetPropertyInfo(memberExp);
                if (property != null)
                {
                    return new OmitMemberTypeByType(typeof(TDeclaringType), property.PropertyType);
                }

                var field = GetFieldInfo(memberExp);
                if (field != null)
                {
                    return new OmitMemberTypeByType(typeof(TDeclaringType), field.FieldType);
                }

                throw new ArgumentException("Only field or property selectors allowed");
            }


            private static PropertyInfo GetPropertyInfo(MemberExpression memberExp)
            {
                if (!(memberExp.Member is PropertyInfo property))
                {
                    return null;
                }

                return property;
            }

            private static MemberExpression GetMemberExpression<TMemberType>(Expression<Func<TDeclaringType, TMemberType>> select)
            {
                if (!(select.Body is MemberExpression memberExp))
                {
                    throw new ArgumentException("only member expressions are allowed");
                }

                return memberExp;
            }

            private static FieldInfo GetFieldInfo(MemberExpression memberExp)
            {
                if (!(memberExp.Member is FieldInfo field))
                {
                    return null;
                }

                return field;
            }
        }
    }

    public class OmitMemberTypeByType : ISpecimenBuilder
    {
        private Type _declaringType;
        private Type _memberType;

        public OmitMemberTypeByType(Type declaringType, Type memberType)
        {
            _declaringType = declaringType;
            _memberType = memberType;
        }

        public object Create(object request, ISpecimenContext context)
        {
            if ((!(request is PropertyInfo propertyInfo)
                || propertyInfo.DeclaringType != _declaringType
                || propertyInfo.PropertyType != _memberType) 
                && 
                (!(request is FieldInfo fieldInfo)
                || fieldInfo.DeclaringType != _declaringType
                || fieldInfo.FieldType != _memberType))
            {
                return new NoSpecimen();
            }

            return new OmitSpecimen();
        }
    }

用作

_fixture.Customizations.Add(new PropertyTypeOmission().For<State>().Omitting(s => s.SqMiles));

【问题讨论】:

    标签: autofixture


    【解决方案1】:

    您提供的自定义示例为我抛出了异常。 这样做是因为Employees 属性没有被初始化,然后其中一个当然不能.Add() 任何东西。

    这对我有用:

    List<Employee> EmployeesFactory((List<Employee> employees, Employee employee) input)
    {
        input.employee.Name = "simple name";
        input.employees.Add(input.employee);
        return input.employees;
    }
    
    _fixture
        .Customize<Department>(c =>
            c.With<List<Employee>, (List<Employee>, Employee)>(
                x => x.Employees,
                EmployeesFactory));
    
    

    【讨论】:

    • 您正在使用.CreateMany(4),每个员工的.Name 等于Simple Name。这不是我要的。我想让 AutoFixture 用匿名对象填充Department.Employees auto 属性,然后我想用Simple Name 添加1 Employee。
    • 如果属性在定制器中被“触及”,似乎 autofixture 不会创建员工。我不知道如何改变这种行为。因此我编辑了我的答案,因此自定义生成了您要求的数据。 @JoshGust
    • 我没有问题找到解决办法。 documentation 似乎说我应该能够像我的问题一样编写我的自定义并期待4 or 1(因为我还没有看到这项工作我不能说我是否应该期待我的自定义 obj 被添加到或替换创建的前 3 个)。
    • 它在 Mark Seemans 示例中起作用的原因可以在您链接到的the post preceding 中找到。它表明MainViewModel 上的AvailableItems 属性的支持字段不是由AutoFixture 初始化的,而是由MainViewModels 构造函数初始化的。 (this.availableItems = new List&lt;MyClass&gt;();) @JoshGust
    • 那更有意义。在属性上有一个 setter 允许 AF 填充它。 是否有任何文档说明这种行为会覆盖像我这样的自定义项中分配的值? 我知道在 .Do(...) 之前使用 .Without(d =&gt; d.Employees) 会阻止构建器在我完成后通过 setter 进行分配在列表中调用.Add()。所以,f.Customize&lt;D&gt;(c=&gt;c.Without(d=&gt;d.Es).Do(d=&gt;{d.Es.Add(f.CreateMany&lt;E&gt;(3)); d.Es.Add(f.Build&lt;E&gt;().With(e=&gt;e.Name, "simple").Create());})); 应该可以了,对吧?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-28
    相关资源
    最近更新 更多