5

I use VS2008 targetting .NET 2.0 Framework, and, just in case, no I can't change this :)

I have a DateCalculator class. Its method GetNextExpirationDate attempts to determine the next expiration, internally using DateTime.Today as a baseline date.

As I was writing unit tests, I realized that I wanted to test GetNextExpirationDate for different 'today' dates.

What's the best way to do this? Here are some alternatives I've considered:

  • Expose a property/overloaded method with argument baselineDate and only use it from the unit test. In actual client code, disregard the property/overloaded method in favour of the method that defaults baselineDate to DateTime.Today. I'm reluctant to do this as it makes the public interface of the DateCalculator class awkward.
  • Create a protected field called baselineDate that is internally set to DateTime.Today. When testing, derive a DateCalculatorForTesting from DateCalculator and set baslineDate via the constructor. It keeps the public interface clean, but still isn't great - baselineDate was made protected and a derived class is required, both solely for testing.
  • Use extension methods. I tried this after adding the ExtensionAttribute, then realized it wouldn't work because extension methods can't access private/protected variables. I initially thought this was really quite an elegant solution. :(

I'd be interested in hearing what others think.

Midhun MP
  • 103,496
  • 31
  • 153
  • 200
ck.
  • 1,056
  • 1
  • 14
  • 25
  • Good question... it would be funny to have to wait till a specific date to find your tests failing because you rely on whatever 'today' is and can't increment it :) – slugster Mar 20 '10 at 05:40
  • Duplicate: http://stackoverflow.com/questions/2425721/unit-testing-datetime-now – Mark Seemann Mar 20 '10 at 11:14
  • Thanks for all the input. The date time provider approach was something I should have thought about as well. In this case, however, I think I'll go with Gishu's approach - it seems to be the least work and least invasive to me. – ck. Mar 20 '10 at 22:42

4 Answers4

4

You can use an interface that supplies the baseline date. Normally you would use an implementation that returns DateTime.Today but for testing purposes have one that lets your unit tests supply a date.

This could also be useful if it becomes necessary to use the current date on a datebase server or some other machine that is not necessarily where your code is running.

In fact, here's the same question with some more detailed answers than mine: Unit Testing: DateTime.Now

Community
  • 1
  • 1
Andrew Kennan
  • 13,947
  • 3
  • 24
  • 33
1

One option is to create an internal overload which takes a DateTime, and delegate the real implementation to that:

public DateTime GetNextExpirationDate()
{
  return GetNextExpirationDate(DateTime.Today);
}

internal DateTime GetNextExpirationDate(DateTime after)
{
  // implementation goes here
}

You can then use InternalsVisibleToAttribute to make the overload visible to your test assembly, and your test assembly can then call it with DateTime values of its own choosing.

itowlson
  • 73,686
  • 17
  • 161
  • 157
1

I usually collect calls to the OS inside an abstraction/interface so that I can easily test it.. Just as Andrew as mentioned above.

However given only the need mentioned in the question; I feel 'Subclass and Override' is the simplest and least invasive way to get this done - Option 2 that the OP mentioned.

public class DateCalculator
{
  public DateTime GetNextExpirationDate() {  // call to GetBaseLineDate() to determine result }
  virtual protected GetBaseLineDate() {  return DateTime.Today; }
}

// in your test assembly
public class DateCalcWithSettableBaseLine : DateCalculator
{
  public DateTime BaseLine { get; set;}
  override protected GetBaseLineDate()
  {    return this.BaseLine;  }
}
Gishu
  • 134,492
  • 47
  • 225
  • 308
0

You can change the operating system's time-of-day clock on the computer that's doing the testing. This is easy, and transparent, but watch out for problems with file timestamps.

Scott Smith
  • 3,900
  • 2
  • 31
  • 63