【问题标题】:Create concrete type for abstract property depending on context根据上下文为抽象属性创建具体类型
【发布时间】:2014-08-26 05:38:49
【问题描述】:

我有以下类型层次结构:

public abstract class ResourceId
{
}

public class CarId : ResourceId
{
}

public class PlaneId: ResourceId
{
}

public interface IResource
{
  ResourceId Id { get; set; }
}

public class Plane : IResource
{
  public ResourceId Id { get; set; }
}

public class Car : IResource
{
  public ResourceId Id { get; set; }
}

我希望 AutoFixture 在尝试创建飞机或汽车时创建“正确”类型的 ResourceId。

我尝试使用:

_fixture.Customize<Plane>(composer => 
    composer.With(h => h.Id, _fixture.Create<PlaneId>()));

当然,这会为每个实例创建相同的 ID。我希望每个人都有一个唯一的 ID。

最好的方法是什么?

【问题讨论】:

    标签: c# unit-testing autofixture


    【解决方案1】:

    有一种方法可以满足您的要求,但它需要一个鲜为人知的自定义 API:Register 方法。

    Register 的目的是允许用户为特定类型指定工厂函数。然后,AutoFixture 会将创建该类型的对象委托给该函数。

    Register 的一个显着特点是它能够在需要时提供匿名对象作为参数传递给工厂函数。它通过许多重载来做到这一点。这里是a few examples

    • Register&lt;T&gt;(Func&lt;T&gt; factory) 不向工厂提供任何参数
    • Register&lt;T1, T&gt;(Func&lt;T1, T&gt; factory) 提供一个T1 类型的参数来创建T 类型的对象
    • Register&lt;T1, T2, T&gt;(Func&lt;T1, T2, T&gt; factory) 提供了两个T1T2 类型的参数来创建T 类型的对象

    现在,我们可以使用此功能自定义 CarPlane 类型对象的创建方式,方法是将 CarIdPlanId 的匿名实例分配给它们的 Id 属性:

    [Fact]
    public void Test()
    {
        var fixture = new Fixture();
        fixture.Register<CarId, Car>(id =>
        {
            var resource = new Car { Id = id };
            return resource;
        });
        fixture.Register<PlaneId, Plane>(id =>
        {
            var resource = new Plane { Id = id };
            return resource;
        });
    
        Assert.NotSame(fixture.Create<Car>().Id, fixture.Create<Car>().Id);
        Assert.NotSame(fixture.Create<Plane>().Id, fixture.Create<Plane>().Id);
    }
    

    此测试通过,因为传递给工厂函数的 CarIdPlanId 对象是由 AutoFixture 创建的,因此每次都不同。

    【讨论】:

    • 我认为这可能是最好的方法,但我不想使用我自己的工厂...我希望 AF 创建 Car/Plane 但覆盖 Id 属性的设置。我想我可以在 lambda 中手动执行此操作,但 AF 会先尝试为 Id 分配一些东西?
    【解决方案2】:

    这里有一个简单的方法来做你想做的事:

    [Fact]
    public void HowToUseFixtureToCreatePlanesWithNewIds()
    {
        var fixture = new Fixture();
        fixture.Customize<Plane>(c => c
            .Without(p => p.Id)
            .Do(p => p.Id = fixture.Create<PlaneId>()));
    
        var planes = fixture.CreateMany<Plane>();
    
        Assert.True(planes.Select(p => p.Id).Distinct().Count() > 1);
    }
    

    但是,如果我是你,我会认真考虑简化该类层次结构...

    【讨论】:

    • 这是我使用的解决方案,但它看起来并不像它应该/应该的那样优雅。但话又说回来,正如你所说,我认为我的问题源于一个不雅的层次结构......
    【解决方案3】:

    您必须为每个类自定义创建算法,在此示例中为 CarPlane,如下面的通过测试所示:

    [Fact]
    public void Test()
    {
        var fixture = new Fixture();
        fixture.Customize<Plane>(c => c
            .With(x => x.Id, fixture.Create<PlaneId>()));
        fixture.Customize<Car>(c => c
            .With(x => x.Id, fixture.Create<CarId>()));
    
        var plane = fixture.Create<Plane>();
        var car = fixture.Create<Car>();
    
        Assert.IsType<PlaneId>(plane.Id);
        Assert.IsType<CarId>(car.Id);
    }
    

    【讨论】:

    • 这就是我已经描述过的问题:它创建了相同的 ID。我想要每个实例的唯一 ID。
    • 抱歉,看来我没有完全理解您的问题。你能试试 Enrico Campidoglio 提供的answer 吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-01-27
    • 1970-01-01
    • 1970-01-01
    • 2020-06-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多