Strategies for dealing with DateTime.Now in unit tests

前端 未结 2 1138
说谎
说谎 2021-01-19 02:58

I have business logic that does not perform certain functions on Saturday or Sunday. I want my unit tests to verify that these functions are performed, but the tests will f

2条回答
  •  一向
    一向 (楼主)
    2021-01-19 03:50

    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 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 dependencies that all do different things than "give me the current local time".

    I hope this helps.

提交回复
热议问题