Create concrete type for abstract property depending on context
Asked Answered
T

3

6

I have the following type hierarchy:

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; }
}

I want AutoFixture to create the 'right' type of ResourceId when it tries to create a Plane or Car.

I tried using:

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

But of course that creates the same ID for each instance. I want each to have a unique ID.

What is the best approach for this?

Toscano answered 26/8, 2014 at 5:38 Comment(0)
M
5

There's a way to do what you ask for but it requires a lesser-known customization API: the Register method.

The purpose of Register is to allow the user to specify a factory function for a specific type. AutoFixture will then delegate the creation of objects of that type to the function.

One distinctive feature of Register is that it's able to provide anonymous objects to pass as arguments to the factory function whenever they're needed. It does so through a number of overloads. Here are a few examples:

  • Register<T>(Func<T> factory) doesn't provide any arguments to the factory
  • Register<T1, T>(Func<T1, T> factory) provides one argument of type T1 to create objects of type T
  • Register<T1, T2, T>(Func<T1, T2, T> factory) provides two arguments of type T1 and T2 to create objects of type T

Now, we can use this feature to customize the way objects of type Car and Plane are created by assigning anonymous instances of CarId and PlanId to their Id properties:

[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);
}

This test passes because the CarId and PlanId objects that are passed to the factory functions are created by AutoFixture, thus being different every time.

Monas answered 27/8, 2014 at 8:39 Comment(1)
I think this may be heading toward the best approach, but I do not want to use my own factory...I want AF to create the Car/Plane but override setting of the Id property. I guess I can do that manually in the lambda, but AF will try to assign something to the Id first?Toscano
D
5

Here's a simple way to do what you want:

[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);
}

However, if I were you, I'd seriously consider simplifying that class hierarchy...

Dicks answered 27/8, 2014 at 19:2 Comment(1)
This is the solution I used but it didn't seem as elegant as it could/should be. But then again, as you say, I think my question resulted from an inelegant hierarchy to begin with...Toscano
E
1

You have to customize the creation algorithm for each class, in this example Car and Plane, as the following passing test demonstrates:

[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);
}
Ethylethylate answered 26/8, 2014 at 17:1 Comment(2)
That's gives the problem I already described: It creates the same IDs. I want a unique ID per instance.Toscano
I apologize, it looks that I didn't fully understand your issue. Could you please try the answer that Enrico Campidoglio provided?Ethylethylate

© 2022 - 2024 — McMap. All rights reserved.