Let AutoFixture create DateTime in UTC?
Asked Answered
O

2

16

By default AutoFixture creates DateTime structs in "local, unspecified time".

I have been trying to find a way to configure it to create DateTime structs with UTC kind, but so far unsuccessful.

Is there a way to do this?

Ovation answered 27/3, 2014 at 11:57 Comment(0)
H
22

You can use a Decorator for this (although easier solutions might also exist):

var fixture = new Fixture();
fixture.Customizations.Add(new UtcRandomDateTimeSequenceGenerator());

var dateTime = fixture.Create<DateTime>();
// -> The value of 'dateTime' is in Coordinated Universal Time (UTC).

The UtcRandomDateTimeSequenceGenerator is defined as:

internal class UtcRandomDateTimeSequenceGenerator : ISpecimenBuilder
{
    private readonly ISpecimenBuilder innerRandomDateTimeSequenceGenerator;

    internal UtcRandomDateTimeSequenceGenerator()
    {
        this.innerRandomDateTimeSequenceGenerator = 
            new RandomDateTimeSequenceGenerator();
    }

    public object Create(object request, ISpecimenContext context)
    {
        var result = 
            this.innerRandomDateTimeSequenceGenerator.Create(request, context);
        if (result is NoSpecimen)
            return result;

        return ((DateTime)result).ToUniversalTime();
    }
}
Hunkers answered 27/3, 2014 at 13:7 Comment(0)
K
9

Preamble

AutoFixture was originally build as a tool for Test-Driven Development (TDD), and TDD is all about feedback. In the spirit of GOOS, you should listen to your tests. If the tests are hard to write, you should consider your API design. AutoFixture tends to amplify that sort of feedback, so my first reaction is to challenge your motivation for wanting to do this.

Is DateTime the right type?

If it really matters that DateTime values are in UTC, then perhaps System.DateTime isn't the best data type for the job. Perhaps DateTimeOffset would be a better option?

AutoFixture will happily create DateTimeOffset values for you.

Can you change the value after it's generated?

If you're using AutoFixture to create the primitive values themselves, you could also just convert them after you get them from AutoFixture:

var dt = fixture.Create<DateTime>().ToUniversalTime();

If you really must change AutoFixture's behaviour

However, if you don't control the API you're testing, and those DateTime values are deeply nested in some data structure, you'll need to configure AutoFixture to build DateTime values in UTC.

Here's one way to do it:

public class UtcConverter : ISpecimenBuilder
{
    private readonly ISpecimenBuilder builder;

    public UtcConverter(ISpecimenBuilder builder)
    {
        this.builder = builder;
    }

    public object Create(object request, ISpecimenContext context)
    {
        var t = request as Type;

        // Abort if request not valid for this type
        if (t == null || t != typeof(DateTime))
            return new NoSpecimen(request);

        var specimen = this.builder.Create(request, context);
        if (!(specimen is DateTime))
            return new NoSpecimen(request);
        return ((DateTime)specimen).ToUniversalTime();
    }
}

You can use it like demonstrated by this passing test:

[Fact]
public void ResolveUtcDate()
{
    var fixture = new Fixture();
    fixture.Customizations.Add(
        new UtcConverter(
            new RandomDateTimeSequenceGenerator()));
    var dt = fixture.Create<DateTime>();
    Assert.Equal(DateTimeKind.Utc, dt.Kind);
}
Kentiga answered 27/3, 2014 at 13:16 Comment(3)
Well, I agree with you on the Offset-suggestion - unfortunately that was a "battle" I lost in the early phases of the project :o(. I am testing some code that I have very little control over so it's not really up to me to handle. Howver, I am specifically using #autofixture to create complex types with a bunch of DateTime fields in them. I can manually update the values after the complex type has been created, but I don't really care about the values themselves - I just need them to "pass" a UTC validation check further down the chain that I cannot bypass. I will look into the decorator :o)Ovation
Hey Mark, in the example code you provided: Should this line "if (t == null && t != typeof(DateTime))" be changed to "if (t == null || t != typeof(DateTime))", i.e. "or" instead of "and"?Erastes
@Erastes I think you're right...Kentiga

© 2022 - 2024 — McMap. All rights reserved.