1

I'm trying to learn AutoFixture, and I've got xUnit and NSubstitute and AutoFixture setup to automatically mock out properties with fakes (with AutoFixture.AutoNSubstitute). In other words, if I have the following interface

public interface IFoo
{
    IBar1 Bar1 {get;}
    IBar2 Bar2 {get; set;}
}

Trying to resolve an IFoo will automatically resolve and populate Bar1 and Bar2.

Everything works great for objects with properties of interface, concrete object, or structure types. I'm having a problem getting AutoFixture to automatically create properties of abstract types however.

I have tried using a TypeRelay for the abstract type, so

fixture.Customizations.Add(new TypeRelay(typeof (AbstractBase), typeof (ConcreteChild)));

I have tried specifying it this way,

fixture.Customize<AbstractBase>(
            composer =>
                composer.FromFactory(
                    (string ChildParam1, string ChildParam2) => new ConcreteChild(ConcreteChildParam1, ConcreteChildParam2)));

I have tried using various custom specimen builders

Resolving via the property type:

var pi = request as PropertyInfo;

if (pi != null &&
    pi.PropertyType == typeof(AbstractBase))
    return context.Resolve(typeof(ConcreteChild));

return new NoSpecimen(request);

Resolving via the class type:

var pi = request as Type;

if (pi != null &&
    pi == typeof (AbstractBase))
    return context.Resolve(typeof(ConcreteChild));

return new NoSpecimen(request);

With both of the above solutions, I also tried context.Create<ConcreteChild>()

Finally I have tried using the Register<AbstractBase>(fixture.Create<ConcreteChild>); syntax.

None of them seem to work as far as automatically populating properties on an object.

The irritating thing is that I can explicitly fixture.Create<AbstractBase>(); in an individual test and get the ConcreteChild and then hand-jam everything but that kind of defeats the purpose of AutoFixture no?

Any ideas?

UPDATE:

The abstract class. I've pruned most of the irrelivent stuff, left the ctor code in as I'm assuming it gets called?

public abstract class ChatEntityId 
{
    private string _localName;

    protected ChatEntityId(string chatRoomName, string entityUid, ChatProtocol protocol)
    {
        ErrorChecker.NormalizeToNullIfNotSet(ref chatRoomName);
        ErrorChecker.NormalizeToNullIfNotSet(ref entityUid);
        if (chatRoomName == null && entityUid == null)
        {
            throw new ArgumentException("Both chatRoomName and entityUid may not be null at the same time.");
        }

        ChatRoomName = chatRoomName;
        EntityUid = entityUid;
        Protocol = protocol;
    }

    public string ChatRoomName { get; private set; }

    public string EntityUid { get; private set; }

    public bool Equals(ChatEntityId chatEntityId) { }

    public override bool Equals(object obj) { }

    public override int GetHashCode() {}

    public string LocalName { get; }

    public ChatProtocol Protocol { get; private set; }

    public override string ToString() { }
}

ChatProtocol is an enum, fairly standard.

The AutoPopulatedProperty ICustomization

    public virtual void Customize(IFixture fixture)
    {
        fixture.Customize(new DomainCustomization());

        // Replacement for the AutoNSubstituteCustomization, this Postprocessor will automatically create fake objects on properties.
        fixture.ResidueCollectors.Add(
            new Postprocessor(
                new NSubstituteBuilder(
                    new MethodInvoker(
                        new NSubstituteMethodQuery())),
                new AutoPropertiesCommand(
                    new PropertiesOnlySpecification())));
    }

    private class PropertiesOnlySpecification : IRequestSpecification
    {
        public bool IsSatisfiedBy(object request)
        {
            return request is PropertyInfo;
        }
    }
4

1 回答 1

2

有点尴尬的是,我意识到 NSubstitute 有它所谓的递归模拟,这部分是我想要的,并解释了为什么我无法弄清楚一些自动模拟的属性来自哪里。问题是它并没有全面做到这一点(可能是正确的),据我所知,在这方面并没有真正的可扩展性。

现在 AutoFixture 开始发挥作用,在我们在 Postprocessors 中创建样本后NSubstituteBuilderAutoPropertiesCommand调用该类并获取它确定的适当属性以填充数据。

不幸的是,两个相关类中的任何逻辑(有一个从泛型AutoPropertiesCommand类继承的非泛型)都不是可覆盖的,这就是问题发生的地方。具体来说,AutoPropertiesCommand<T>有两种方法用于获取属性和字段,然后使用提供的IRequestSpecificationPropertiesOnlySpecification在本例中)进行过滤。

有问题的方法

    private IEnumerable<PropertyInfo> GetProperties(object specimen)
    {
        return from pi in this.GetSpecimenType(specimen).GetProperties(BindingFlags.Public | BindingFlags.Instance)
               where pi.GetSetMethod() != null
               && pi.GetIndexParameters().Length == 0
               && this.specification.IsSatisfiedBy(pi)
               select pi;
    }

所以这里的解决方案是要么提供我自己的AutoPropertiesCommand实现而没有上述限制,要么为我遇到的每个案例显式创建自定义。还没有决定我更喜欢哪种方法,但可能是前者。

作为一个侧边栏,没有将这两种方法作为受保护的虚拟方法似乎有点限制,除了“它只是以这种方式编码”之外,还有什么特别的原因吗?我相信这些是基本的 AutoFixture 类,以供记录。

于 2014-07-09T13:24:30.170 回答