To aid unit-testing we have wrapped up the DateTime
class in a delegate so that DateTime.Now
can be overridden in a unit-test.
public static class SystemTime
{
#region Static Fields
public static Func<DateTime> Now = () => DateTime.Now;
#endregion
}
Here is an example of its use in an xunit unit-test:
[Fact]
public void it_should_update_the_last_accessed_timestamp_on_an_entry()
{
// Arrange
var service = this.CreateClassUnderTest();
var expectedTimestamp = SystemTime.Now();
SystemTime.Now = () => expectedTimestamp;
// Act
service.UpdateLastAccessedTimestamp(this._testEntry);
// Assert
Assert.Equal(expectedTimestamp, this._testEntry.LastAccessedOn);
}
The test runs fine locally, however it is failing on our build server as the datetimes differ in the Assert
statement.
I'm struggling to think of a reason why it would fail given that the DateTime
is mocked via the above mentioned delegate wrapper. I've verified there are no issues in the implementation of the UpdateLastAccessedTimestamp
method and that the test passes when run locally.
Unfortunately I can't debug it on our build server. Any ideas why it would fail only when run on the build server?
Note that the implementation of UpdateLastAccessedTimestamp
is as follows:
public void UpdateLastAccessedTimestamp(Entry entry)
{
entry.LastAccessedOn = SystemTime.Now();
this._unitOfWork.Entries.Update(entry);
this._unitOfWork.Save();
}
The Entry
class is just a simple POCO class that has a number of fields including the LastAccessedOn
field:
public class Entry
{
public DateTime LastAccessedOn { get; set; }
//other fields that have left out to keep the example concise
}
Your issue could be due to multiple unit tests working with the static SystemTime
. If you were to for example have something like:
Unit test 1
[Fact]
public void it_should_update_the_last_accessed_timestamp_on_an_entry()
{
// Arrange
var service = this.CreateClassUnderTest();
var expectedTimestamp = SystemTime.Now();
SystemTime.Now = () => expectedTimestamp;
// Act
service.UpdateLastAccessedTimestamp(this._testEntry);
// Assert
Assert.Equal(expectedTimestamp, this._testEntry.LastAccessedOn);
}
public void UpdateLastAccessedTimestamp(Entry entry)
{
entry.LastAccessedOn = SystemTime.Now();
this._unitOfWork.Entries.Update(entry);
this._unitOfWork.Save();
}
Unit test 2
[Fact]
public void do_something_different
{
SystemTime.Now = () => DateTime.Now;
}
So let's assume that unit test 2 (it's entirety) fires off in between the lines in unit test 1 of:
SystemTime.Now = () => expectedTimestamp;
// Unit test 2 starts execution here
// Act
service.UpdateLastAccessedTimestamp(this._testEntry);
If this scenario were to occur, then your UpdateLastAccessedTimestamp
would not (necessarily) have your expected DateTime
value that you set at SystemTime.Now = () => expectedTimestamp;
, as another test has since overwritten the function you have provided from unit test 1.
This is why I think you're probably better off either passing the DateTime
in as a parameter, or using an injectable datetime as so:
/// <summary>
/// Injectable DateTime interface, should be used to ensure date specific logic is more testable
/// </summary>
public interface IDateTime
{
/// <summary>
/// Current Data time
/// </summary>
DateTime Now { get; }
}
/// <summary>
/// DateTime.Now - use as concrete implementation
/// </summary>
public class SystemDateTime : IDateTime
{
/// <summary>
/// DateTime.Now
/// </summary>
public DateTime Now { get { return DateTime.Now; } }
}
/// <summary>
/// DateTime - used to unit testing functionality around DateTime.Now (externalizes dependency on DateTime.Now
/// </summary>
public class MockSystemDateTime : IDateTime
{
private readonly DateTime MockedDateTime;
/// <summary>
/// Take in mocked DateTime for use in testing
/// </summary>
/// <param name="mockedDateTime"></param>
public MockSystemDateTime(DateTime mockedDateTime)
{
this.MockedDateTime = mockedDateTime;
}
/// <summary>
/// DateTime passed from constructor
/// </summary>
public DateTime Now { get { return MockedDateTime; } }
}
Using this scenario, your service class could change from (something like) this:
public class Service
{
public Service() { }
public void UpdateLastAccessedTimestamp(Entry entry)
{
entry.LastAccessedOn = SystemTime.Now();
this._unitOfWork.Entries.Update(entry);
this._unitOfWork.Save();
}
}
To this:
public class Service
{
private readonly IDateTime _iDateTime;
public Service(IDateTime iDateTime)
{
if (iDateTime == null)
throw new ArgumentNullException(nameof(iDateTime));
// or you could new up the concrete implementation of SystemDateTime if not provided
_iDateTime = iDateTime;
}
public void UpdateLastAccessedTimestamp(Entry entry)
{
entry.LastAccessedOn = _iDateTime.Now;
this._unitOfWork.Entries.Update(entry);
this._unitOfWork.Save();
}
}
For the actual implementation of your Service
you could new up like (or use an IOC container):
Service service = new Service(new SystemDateTime());
For testing you could either use a mocking framework, or your Mock class as such:
Service service = new Service(new MockDateTime(new DateTime(2000, 1, 1)));
And your unit test could become:
[Fact]
public void it_should_update_the_last_accessed_timestamp_on_an_entry()
{
// Arrange
MockDateTime mockDateTime = new MockDateTime(new DateTime 2000, 1, 1);
var service = this.CreateClassUnderTest(mockDateTime);
// Act
service.UpdateLastAccessedTimestamp(this._testEntry);
// Assert
Assert.Equal(mockDateTime.Now, this._testEntry.LastAccessedOn);
}
You are just lucky it works local. To get this working you have to stub the place where your service gets its last access datatime and check against the time return there. At this moment you local machine is fast enough to return 2 times the same time on DataTime.Now and your build server is not.