How to implement date restrictions with AutoFixture?
Asked Answered
U

3

19

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.

Unsuccess answered 19/2, 2015 at 10:21 Comment(0)
S
52

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.

Scotia answered 20/2, 2015 at 16:34 Comment(8)
This is a great answer. Just to add to it, I presume it's possible that SomeClass actually has behavior that sets these, such as Activate() or Expire() methods. Perhaps these properties don't even need setters!Pineapple
+1 This should be the accepted answer. Besides the great design feedback, the AutoFixture examples presented in this answer are simpler than mine.Pyromancy
Thank's for this detailed answer!Unsuccess
+1 for the TimeSpan solution. However, the Postel's Law solution seems un-intuitive to me. I can't quite put my finger on why, but this kind of logic in the constructor feels like 'dishonesty'... I would expect the ordering of parameters to be important, as they usually are in programming. Perhaps if this were abstracted to a dedicated Period class with a static factory method then the restrictions on valid would be clearer. I'll try and put this in an answer to demonstrate what I meanShrader
@AlexG You probably don't like it because it's too implicit, and you think that explicit is better than implicit. To be honest, I'd tend to agree with you in this case, but I wanted to sketch various different solutions, which is the reason I also added the Postel's Law solution.Scotia
Yes, that's exactly right, I just wanted it to be more explicit. Have done so in answer belowShrader
Excellent explanation. But what if you are testing code which relies on a type that, by design, does not encapsulated business logic e.g. a DTO? Perhaps I have a validator which rejects DTOs before that get to my SUT so my SUT can expect only valid data. I suppose this design still fails to encapsulate valid vs invalid state. HmmmMichaud
We tend to write such code in financial analysis apps that run for a really long time, because it sucks to get a thrown exception in the middle of some huge computation or find out at the end that your answer is 0 or some crazy value because your data was backwards.Coercive
S
5

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.

Shrader answered 23/2, 2015 at 13:8 Comment(0)
P
3

Since SomeClass is mutable, here's one way of doing it:

[Fact]
public void UsingGeneratorOfDateTime()
{
    var fixture = new Fixture();
    var generator = fixture.Create<Generator<DateTime>>();
    var sut = fixture.Create<SomeClass>();
    var seed = fixture.Create<int>();

    sut.ExpirationDate =
        generator.First().AddYears(seed);
    sut.ValidFrom =
        generator.TakeWhile(dt => dt < sut.ExpirationDate).First();

    Assert.True(sut.ValidFrom < sut.ExpirationDate);
}

FWIW, using AutoFixture with xUnit.net data theories, the above test can be written as:

[Theory, AutoData]
public void UsingGeneratorOfDateTimeDeclaratively(
    Generator<DateTime> generator,
    SomeClass sut,
    int seed)
{
    sut.ExpirationDate =
        generator.First().AddYears(seed);
    sut.ValidFrom =
        generator.TakeWhile(dt => dt < sut.ExpirationDate).First();

    Assert.True(sut.ValidFrom < sut.ExpirationDate);
}
Pyromancy answered 19/2, 2015 at 16:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.