Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement date restrictions with AutoFixture?

Tags:

c#

autofixture

I'm currently having a model class which contains several properties. A simplified model could look like this:

public class SomeClass
{
    public DateTime ValidFrom { get; set; }
    public DateTime ExpirationDate { get; set; }
}

Now I'm implementing some unit tests by using NUnit and use AutoFixture to create some random data:

[Test]
public void SomeTest()
{ 
    var fixture = new Fixture();
    var someRandom = fixture.Create<SomeClass>();
}

This works perfect so far. But there is the requirement that the date of ValidFrom is always before ExpirationDate. I have to ensure this since I'm implementing some positive tests.

So is there an easy way to implement this by using AutoFixture? I know I could create a fix date and add a random date interval to solve this, but it would be great if AutoFixture could handle this requirement itself.

I haven't got a lot of experience with AutoFixture, but I know I can get an ICustomizationComposer by calling the Build method:

var fixture = new Fixture();
var someRandom = fixture.Build<SomeClass>()
    .With(some => /*some magic like some.ValidFrom < some.ExpirationDate here...*/ )
    .Create();

Maybe this is the right way to achieve this?

Thanks in advance for any help.

like image 948
oopbase Avatar asked Feb 19 '15 10:02

oopbase


2 Answers

It may be tempting to ask the question of how do I make AutoFixture adapt to my design?, but often, a more interesting question could be: how do I make my design more robust?

You can keep the design and 'fix' AutoFixture, but I don't think it's a particularly good idea.

Before I tell you how to do that, depending on your requirements, perhaps all you need to do is the following.

Explicit assignment

Why not simply assign a valid value to ExpirationDate, like this?

var sc = fixture.Create<SomeClass>();
sc.ExpirationDate = sc.ValidFrom + fixture.Create<TimeSpan>();

// Perform test here...

If you're using AutoFixture.Xunit, it can be even simpler:

[Theory, AutoData]
public void ExplicitPostCreationFix_xunit(
    SomeClass sc,
    TimeSpan duration)
{
    sc.ExpirationDate = sc.ValidFrom + duration;

    // Perform test here...
}

This is fairly robust, because even though AutoFixture (IIRC) creates random TimeSpan values, they'll stay in the positive range unless you've done something to your fixture to change its behaviour.

This approach would be the simplest way to address your question if you need to test SomeClass itself. On the other hand, it's not very practical if you need SomeClass as input values in myriads of other tests.

In such cases, it can be tempting to fix AutoFixture, which is also possible:

Changing AutoFixture's behaviour

Now that you've seen how to address the problem as a one-off solution, you can tell AutoFixture about it as a general change of the way SomeClass is generated:

fixture.Customize<SomeClass>(c => c
    .Without(x => x.ValidFrom)
    .Without(x => x.ExpirationDate)
    .Do(x => 
        {
            x.ValidFrom = fixture.Create<DateTime>();
            x.ExpirationDate = 
                x.ValidFrom + fixture.Create<TimeSpan>();
        }));
// All sorts of other things can happen in between, and the
// statements above and below can happen in separate classes, as 
// long as the fixture instance is the same...
var sc = fixture.Create<SomeClass>();

You can also package the above call to Customize in an ICustomization implementation, for further reuse. This would also enable you to use a customized Fixture instance with AutoFixture.Xunit.

Change the design of the SUT

While the above solutions describe how to change the behaviour of AutoFixture, AutoFixture was originally written as a TDD tool, and the main point of TDD is to provided feedback about the System Under Test (SUT). AutoFixture tends to amplify that sort of feedback, which is also the case here.

Consider the design of SomeClass. Nothing prevents a client from doing something like this:

var sc = new SomeClass
{
    ValidFrom = new DateTime(2015, 2, 20),
    ExpirationDate = new DateTime(1900, 1, 1)
};

This compiles and runs without errors, but is probably not what you want. Thus, AutoFixture is actually not doing anything wrong; SomeClass isn't properly protecting its invariants.

This is a common design mistake, where developers tend to put too much trust into the semantic information of the members' names. The thinking seems to be that no-one in their right mind would set ExpirationDate to a value before ValidFrom! The problem with that sort of argument is that it assumes that all developers will always be assigning these values in pairs.

However, clients may also get a SomeClass instance passed to them, and want to update one of the values, e.g.:

sc.ExpirationDate = new DateTime(2015, 1, 31);

Is this valid? How can you tell?

The client could look at sc.ValidFrom, but why should it? The whole purpose of encapsulation is to relieve clients of such burdens.

Instead, you should consider changing the design SomeClass. The smallest design change I can think of is something like this:

public class SomeClass
{
    public DateTime ValidFrom { get; set; }
    public TimeSpan Duration { get; set; }
    public DateTime ExpirationDate
    {
        get { return this.ValidFrom + this.Duration; }
    }
}

This turns ExpirationDate into a read-only, calculated property. With this change, AutoFixture just works out of the box:

var sc = fixture.Create<SomeClass>();

// Perform test here...

You can also use it with AutoFixture.Xunit:

[Theory, AutoData]
public void ItJustWorksWithAutoFixture_xunit(SomeClass sc)
{
    // Perform test here...
}

This is still a little brittle, because although by default, AutoFixture creates positive TimeSpan values, it's possible to change that behaviour as well.

Furthermore, the design actually allows clients to assign negative TimeSpan values to the Duration property:

sc.Duration = TimeSpan.FromHours(-1);

Whether or not this should be allowed is up to the Domain Model. Once you begin to consider this possibility, it may actually turn out that defining time periods that move backwards in time is valid in the domain...

Design according to Postel's Law

If the problem domain is one where going back in time isn't allowed, you could consider adding a Guard Clause to the Duration property, rejecting negative time spans.

However, personally, I often find that I arrive at a better API design when I take Postel's Law seriously. In this case, why not change the design so that SomeClass always uses the absolute TimeSpan instead of the signed TimeSpan?

In that case, I'd prefer an immutable object that doesn't enforce the roles of two DateTime instances until it knows their values:

public class SomeClass
{
    private readonly DateTime validFrom;
    private readonly DateTime expirationDate;

    public SomeClass(DateTime x, DateTime y)
    {
        if (x < y)
        {
            this.validFrom = x;
            this.expirationDate = y;
        }
        else
        {
            this.validFrom = y;
            this.expirationDate = x;
        }
    }

    public DateTime ValidFrom
    {
        get { return this.validFrom; }
    }

    public DateTime ExpirationDate
    {
        get { return this.expirationDate; }
    }
}

Like the previous redesign, this just works out of the box with AutoFixture:

var sc = fixture.Create<SomeClass>();

// Perform test here...

The situation is the same with AutoFixture.Xunit, but now no clients can misconfigure it.

Whether or not you find such a design appropriate is up to you, but I hope at least it's food for thought.

like image 79
Mark Seemann Avatar answered Oct 07 '22 16:10

Mark Seemann


This is a kind of "extended comment" in reference to Mark's answer, trying to build on his Postel's Law solution. The parameter swapping in the constructor felt uneasy for me, so I've made the date swapping behaviour explicit in a Period class.

Using C#6 syntax for brevity:

public class Period
{
    public DateTime Start { get; }
    public DateTime End { get; }

    public Period(DateTime start, DateTime end)
    {
        if (start > end) throw new ArgumentException("start should be before end");
        Start = start;
        End = end;
    }

    public static Period CreateSpanningDates(DateTime x, DateTime y, params DateTime[] others)
    {
        var all = others.Concat(new[] { x, y });
        var start = all.Min();
        var end = all.Max();
        return new Duration(start, end);
    }
}

public class SomeClass
{
    public DateTime ValidFrom { get; }
    public DateTime ExpirationDate { get; }

    public SomeClass(Period period)
    {
        ValidFrom = period.Start;
        ExpirationDate = period.End;
    }
}

You would then need to customize your fixture for Period to use the static constructor:

fixture.Customize<Period>(f =>
    f.FromFactory<DateTime, DateTime>((x, y) => Period.CreateSpanningDates(x, y)));

I think the main benefit of this solution is that it extracts the time-ordering requirement into its own class (SRP) and leaves your business logic to be expressed in terms of an already-agreed contract, apparent from the constructor signature.

like image 41
Alex Avatar answered Oct 07 '22 16:10

Alex