开发者

How to test logic which is dependent on current date [duplicate]

This question already has answers here: Unit Testing: DateTime.Now (22 answers) Closed 10 months ago.

I have this method which is dependent on current date. It checks if today is Sun, Mon, Tue or Wed, then it gives 5 days of lead time for arrival of shipped items. If its Thur, Fri or Sat then it gives 6 days of lead time to account for the weekend.

private DateTime GetEstimatedArrivalDate()
{
    DateTime estimatedDate; 
    if (DateTime.N开发者_Python百科ow.DayOfWeek >= DayOfWeek.Thursday)
    {
        estimatedDate = DateTime.Now.Date.AddDays(6);
    }
    else
    {
        estimatedDate = DateTime.Now.Date.AddDays(5);
    }
    return estimatedDate; 
}

The actual estimation logic is more complex. I have simplified it for the purpose of this question. My question is how do I write a unit test for something like this which depends on todays date?


You need to pass the current date in as a parameter:

private DateTime GetEstimatedArrivalDate(DateTime currentDate)
{
    DateTime estimatedDate; 
    if (currentDate.DayOfWeek >= DayOfWeek.Thursday)
    {
        estimatedDate = currentDate.AddDays(6);
    }
    else
    {
        estimatedDate = currentDate.AddDays(5);
    }
    return estimatedDate; 
}

In real code you call it like this:

DateTime estimatedDate = GetEstimatedArrivalDate(DateTime.Now.Date);

Then you can test it as follows:

DateTime actual = GetEstimatedArrivalDate(new DateTime(2010, 2, 10));
DateTime expected = ...;
// etc...

Note that this also fixes a potential bug in your program where the date changes between consecutive calls to DateTime.Now.


Generally speaking, you'd want to abstract the method of obtaining the current date and time behind an interface, eg:

public interface IDateTimeProvider
{
    DateTime Now { get; }
}

The real service would be:

public class DateTimeProvider: IDateTimeProvider
{
    public DateTime Now
    {
        get
        {
            return DateTime.Now;
        }
    }
}

And a test service would be:

public class TestDateTimeProvider: IDateTimeProvider
{
    private DateTime timeToProvide;
    public TestDateTimeProvider(DateTime timeToProvide)
    {
        this.timeToProvide = timeToProvide;
    }

    public DateTime Now
    {
        get
        {
            return timeToProvide;
        }
    }
}

For services that require the current time, have them take an IDateTimeProvider as a dependency. For the real thing, pass a new DateTimeProvider(); when you're a component, pass in a new TestDateTimeProvider(timeToTestFor).


Make your class take an IClock parameter (via constructor or property)

interface IClock
{
    DateTime Now { get; }
}

You can then use a fake implementation for testing

class FakeClock : IClock
{
    DateTime Now { get; set }
}

and a real implementation the rest of the time.

class SystemClock : IClock
{
    DateTime Now { get { return DateTime.Now; } }
}


I would suggest doing this as Mark suggests, but with the addition of a overloaded call for production use that takes no parameter and uses DateTime.Now

private DateTime GetEstimatedArrivalDate()
{
    return GetEstimatedArrivalDate(DateTime.Now);
}

private DateTime GetEstimatedArrivalDate(DateTime currentDate)
{
    DateTime estimatedDate; 
    if (currentDate.DayOfWeek >= DayOfWeek.Thursday)
    {
        estimatedDate = currentDate.AddDays(6);
    }
    else
    {
        estimatedDate = currentDate.AddDays(5);
    }
    return estimatedDate; 
}


One "common" way of doing so is to "fake" the current system date (that can be done in several ways) and then test your code on "known" dates.

Another interesting way is to change your implementation slightly to:

private DateTime GetEstimatedArrivalDate()
{
    return GetEstimatedArrivalDate(DateTime.Now);
}

private DateTime GetEstimatedArrivalDate(DateTime forDate)
{
    DateTime estimatedDate; 
    if (forDate.DayOfWeek >= DayOfWeek.Thursday)
    {
        estimatedDate = forDate.Date.AddDays(6);
    }
    else
    {
        estimatedDate = forDate.Date.AddDays(5);
    }
    return estimatedDate; 
}

And then use the method with a parameter to test on "immediate" dates.


Seems like there are a limited enough number of cases that you could test them each explicitly. The method depends on today's date, but the output depends only on the day of week, and every date has a day of week.


You could pass in a delegate that returns DateTime.Now during normal execution, and then in your test pass in another delegate that returns a fixed date, and assert your result based on that.


I'll give the controversial answer, don't test it.

The logic is trivial and it has zero dependencies, i believe in good code coverage but not when it increases complexity for no real gain.

0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜