JustDummies

Just dummies — but seriously powerful ones.

Documentation sections

Dates and times

Five generators cover the temporal types. They share an ordering vocabulary, and two of them add a dimension the others do not have: granularity, and the offset.

The five generators

FactoryDrawsAvailability
Any.DateTime()DateTimeeverywhere
Any.DateTimeOffset()DateTimeOffseteverywhere
Any.TimeSpan()TimeSpaneverywhere
Any.DateOnly()DateOnly.NET 8+
Any.TimeOnly()TimeOnly.NET 8+

Ordering

Instants order rather than compare in size, so the vocabulary reads temporally — After and Before rather than GreaterThan and LessThan:

DateTime ordered   = Any.DateTime().Between(new DateTime(2020, 1, 1), new DateTime(2025, 12, 31)).Generate();
DateTime recent    = Any.DateTime().After(new DateTime(2024, 1, 1)).Generate();
DateTime onOrAfter = Any.DateTime().AfterOrEqualTo(new DateTime(2024, 1, 1)).Generate();
DateTime past      = Any.DateTime().Before(new DateTime(2030, 1, 1)).Generate();
DateTime onOrBefore = Any.DateTime().BeforeOrEqualTo(new DateTime(2030, 1, 1)).Generate();

TimeSpan is a duration rather than an instant, so it keeps the numeric vocabulary and adds the sign family:

TimeSpan timeout  = Any.TimeSpan().Between(TimeSpan.FromSeconds(1), TimeSpan.FromMinutes(5)).Generate();
TimeSpan positive = Any.TimeSpan().Positive().Generate();
TimeSpan nonZero  = Any.TimeSpan().NonZero().Generate();
TimeSpan shorter  = Any.TimeSpan().LessThan(TimeSpan.FromHours(1)).Generate();

Granularity

WithGranularity snaps the drawn value onto a grid. It is what turns a raw instant into one your domain would actually store:

// Whole minutes: no stray seconds or ticks to break an equality assertion.
DateTime appointment = Any.DateTime()
                          .Between(new DateTime(2025, 1, 1), new DateTime(2025, 12, 31))
                          .WithGranularity(TimeSpan.FromMinutes(1))
                          .Generate();

// Whole days.
DateTime businessDay = Any.DateTime().WithGranularity(TimeSpan.FromDays(1)).Generate();

// A duration in whole seconds.
TimeSpan retryAfter = Any.TimeSpan().Positive().WithGranularity(TimeSpan.FromSeconds(1)).Generate();

Without it, a drawn instant carries sub-second precision, and a test that round-trips it through a store that truncates to seconds fails for a reason that has nothing to do with the code under test. Declaring the granularity your storage actually has is the fix, and it is a genuine domain invariant rather than a workaround.

DateOnly has no granularity constraint — it is already a whole day.

DateTimeOffset varies on two dimensions

A DateTimeOffset is an instant and an offset from UTC, and JustDummies varies both. That is deliberate: a dummy that always carried +00:00 would never find the code that assumes local time (ADR-0016 opens in a new tab).

DateTimeOffset anywhere = Any.DateTimeOffset().Generate();                       // instant and offset both vary
DateTimeOffset utc      = Any.DateTimeOffset().WithOffset(TimeSpan.Zero).Generate();
DateTimeOffset european = Any.DateTimeOffset()
                             .WithOffsetBetween(TimeSpan.FromHours(0), TimeSpan.FromHours(3))
                             .Generate();

Declaring an offset filters the pool of instants rather than rewriting the instant that was drawn: the pair you get is one that genuinely exists together (ADR-0030 opens in a new tab).

The DateTime.Now trap

This is the mistake worth naming, because it survives review and fails at midnight:

// Fragile: the dummy is drawn relative to a clock the test does not control.
DateTime createdAt = Any.DateTime().Before(DateTime.Now).Generate();

The test now depends on when it runs. Worse, it is not reproducible: replaying the seed replays the draw, not the clock.

Pin an instant the test owns, and constrain relative to that:

DateTime now       = new DateTime(2025, 6, 15, 12, 0, 0, DateTimeKind.Utc);
DateTime createdAt = Any.DateTime().Before(now).AfterOrEqualTo(now.AddYears(-1)).Generate();

Membership and exclusion

Every temporal generator carries the usual pool and exclusion family:

DateTime quarterEnd = Any.DateTime()
                         .OneOf(new DateTime(2025, 3, 31), new DateTime(2025, 6, 30), new DateTime(2025, 9, 30))
                         .Generate();

DateTime notEpoch = Any.DateTime()
                       .Between(new DateTime(2020, 1, 1), new DateTime(2025, 1, 1))
                       .DifferentFrom(new DateTime(2020, 1, 1))
                       .Generate();

DateOnly and TimeOnly

Available on .NET 8 and above, with the same ordering vocabulary:

DateOnly dueDate = Any.DateOnly()
                      .Between(new DateOnly(2025, 1, 1), new DateOnly(2025, 12, 31))
                      .Generate();

TimeOnly openingTime = Any.TimeOnly()
                          .Between(new TimeOnly(8, 0), new TimeOnly(20, 0))
                          .WithGranularity(TimeSpan.FromMinutes(15))
                          .Generate();

On netstandard2.0 those types do not exist, so neither do their factories. Everything else on this page is available on every target.

Read the source, or correct it there opens in a new tab· Mirrored from lib-v1.0.0-preview.6