开发者

Strategies for dealing with DateTime.Now in unit tests

开发者 https://www.devze.com 2023-02-02 15:58 出处:网络
I have business logic that does not perform certain functions on Saturday or Sunday.I want my unit tests to ve开发者_如何学JAVArify that these functions are performed, but the tests will fail on Satur

I have business logic that does not perform certain functions on Saturday or Sunday. I want my unit tests to ve开发者_如何学JAVArify that these functions are performed, but the tests will fail on Saturday/Sunday.

I figure the easiest route is to have the unit tests convey a friendly message stating that the test results are not valid if they are run on Saturday/Sunday.

With C# / NUnit...

Assert.That(
  DateTime.Now.DayOfWeek != DayOfWeek.Sunday && DateTime.Now.DayOfWeek !=      
  DayOfWeek.Saturday, "This test cannot be run on Saturday or Sunday");

Is it possible/advisable to try and mock the date? Are there other strategies for handling this scenario?


Is it possible/advisable to try and mock the date?

Yes, not only that it is advisable but it is the only way to reliably unit test your code in isolation. Suppose that you wanted to test the following (meaningless) method:

public bool Foo()
{
    return (DateTime.Now.DayOfWeek == DayOfWeek.Sunday);
}

It's more than obvious that you can't test it because it relies on a static method (the static Now property to be more precise). It's clear that components which are tightly coupled like this cannot be unit tested in isolation.

Now consider this improvement (separation of concerns with constructor DI):

private readonly Func<DateTime> _nowProvider;
public SomeClass(Func<DateTime> nowProvider)
{
    _nowProvider = nowProvider;
}

public bool Foo()
{
    return (_nowProvider().DayOfWeek == DayOfWeek.Sunday);
}

Now that's much better and easier to unit test. SomeClass no longer depends on a non-deterministic date. In the real application you would obviously instantiate SomeClass like this:

var s = new SomeClass(() => DateTime.Now);
s.Foo();

and in your unit test you will mock it so that you can verify both cases:

var subjectUnderTest = new SomeClass(() => new DateTime(2011, 1, 3));
var actual = subjectUnderTest.Foo();
// assertions, ...


You should create an abstraction over the system clock, as you should create abstractions over other system resources and external resources to be able to write RTM unit tests.

An abstraction over the system clock is pretty simple and could look like this:

public interface ISystemClock
{
    DateTime Now { get; }
}

Classes in your application that depend on the system clock should than typically have an ISystemClock as constructor argument. For unit testing scenario's you can use an ISystemClock implementation that looks like this:

public class FakeSystemClock : ISystemClock
{
    // Note the setter.
    public DateTime Now { get; set; }
}

Now you can use this fake clock in your unit tests like this:

[TestMethod]
[ExpectedException(typeof(InvalidOperationException),
    "Service should throw an exception when called on saturday.")]
public void DoSomething_WhenCalledOnSaturday_ThrowsException()
{
    // Arrange
    var saturday = new DateTime(2011, 1, 1);
    Assert.AreEqual(saturday.DayOfWeek, DayOfWeek.Saturday,
        "Test setup fail");
    var clock = new FakeSystemClock() { Now = saturday };
    var service = new Service(clock);

    // Act
    service.DoSomething();
}

In your application project you can define an ISystemClock implementation that actually uses the system clock. Using the given ISystemClock definition, it will look like this:

public class RealSystemClock : ISystemClock
{
    public DateTime Now => DateTime.Now;
}

When you use a DI container, you can configure it to wire up your types and automatically inject instances of RealSystemClock when a constructor specifies a ISystemClock argument.

btw. While you could use Func<DateTime> delegates to do the trick, defining an interface for this is much more explicit and much more readable to other developers. Besides that, wiring everything in a DI container would be much easier, because it's so easy to end up with multiple Func<DateTime> dependencies that all do different things than "give me the current local time".

I hope this helps.


Noda Time might be a solution/alternative for new projects as it all interface based and, thus, easily mock-able.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号