I'm working on an app with a domain model similar to this, where a LineItem
can be referenced both from an Order
as well as from a Shipment
.
If I'm using AutoFixture to generate an Order
, how can I use the same set of LineItems
for both order.LineItems
and order.Shipments*.ItemShipments*.LineItem
?
Theoretically, the following test ought to pass:
var fullyShippedOrder = fixture.CreateAnonymous<Order>();
var shippedLineItems = fullyShippedOrder.Shipments
.SelectMany(o => o.ItemShipment, (s, i) => i.LineItem)
.Distinct();
Assert.EqualCollection(fullyShippedOrder.LineItems, shippedLineItems);
... although I would also want to be able to generate partially-shipped orders depending on the test.
(There's a solid argument to be made that a line item on an Order and a line item on a Shipment are different things and I shouldn't be using the same class to represent them. However, the data I'm working with comes from a legacy system and there isn't much that can be done about it. )
As you said, the friction you're experiencing in your tests is most likely a sign of a design problem in the domain model. Ideally, you should listen to your tests and fix the problem at its root. However, granted that it isn't feasible in this case, here's a workaround.
You can configure the Fixture
to always return LineItem
objects out of a fixed sequence by using the Register<T>(Func<T> creator)
method.
Here's an example packaged up in a customization:
public class GenerateLineItemFromFixedSequence : ICustomization
{
public void Customize(IFixture fixture)
{
var items = CreateFixedLineItemSequence(fixture);
fixture.Register(() => GetRandomElementInSequence(items));
}
private static IEnumerable<LineItem> CreateFixedLineItemSequence(IFixture fixture)
{
return fixture.CreateAnonymous<LineItem[]>();
}
private static LineItem GetRandomElementInSequence(IEnumerable<LineItem> items)
{
var randomIndex = new Random().Next(0, items.Count());
return items.ElementAt(randomIndex);
}
}
In order to apply this behavior in the context of a test, just add the customization to the Fixture
object:
fixture.Customize(new GenerateLineItemFromFixedSequence());
Similarly, you could create other customizations that generate the fixed sequence of LineItem
objects in different states, like the partially-shipped orders you mentioned, and use them in different tests.
It's interesting to note that this customization can be made generic, since the algorithm itself isn't coupled with the type of objects being created. This would effectively transform it into a strategy.
So, modifying the customization by introducing a generic parameter:
public class GenerateFromFixedSequence<T> : ICustomization
{
public void Customize(IFixture fixture)
{
var items = CreateFixedSequence(fixture);
fixture.Register(() => GetRandomElementInSequence(items));
}
private static IEnumerable<T> CreateFixedSequence(IFixture fixture)
{
return fixture.CreateAnonymous<T[]>();
}
private static T GetRandomElementInSequence(IEnumerable<T> items)
{
var randomIndex = new Random().Next(0, items.Count());
return items.ElementAt(randomIndex);
}
}
would allow you to use it for different objects:
fixture.Customize(new GenerateFromFixedSequence<LineItem>());
fixture.Customize(new GenerateFromFixedSequence<Order>());
fixture.Customize(new GenerateFromFixedSequence<Shipment>());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With