Inject DateTime.Now to Aid Unit Tests
If you have logic that relies on the current system date, it's often difficult to see how to unit test it. But by injecting a function that returns DateTime.Now
we can stub the current date to be anything we want it to be.
Let's look at an example. Here we have a simple service that creates a new user instance and saves it in a database:
public class UserService : IUserServiceNow if I want to write a unit test that checks that the correct created date is set, I have to rely on the assumption that the system date won't change between the creation of the User instance and the test assertions.
{
private readonly IUserData userData;
public UserService(IUserData userData)
{
this.userData = userData;
}
public void CreateUser(string username)
{
var user = new User(username, createdDateTime: DateTime.UtcNow);
userData.SaveUser(user);
}
}
[TestFixture]But in this case, probably because Rhino Mocks is doing some pretty intensive proxying, a few milliseconds pass between the user being created and my assertions running.
public class UserServiceTests
{
private IUserService sut;
private IUserData userData;
[SetUp]
public void SetUp()
{
userData = MockRepository.GenerateStub<iuserdata>();
sut = new UserService(userData);
}
[Test]
public void UserServiceShouldCreateUserWithCorrectCreatedDate()
{
User user = null;
// using Rhino Mocks to grab the User instance passed to the IUserData stub
userData.Stub(x => x.SaveUser(null)).IgnoreArguments().Callback<user>(x =>
{
user = x;
return true;
});
sut.CreateUser("mike");
Assert.AreEqual(DateTime.UtcNow, user.CreatedDateTime);
}
}
Test 'Mike.Spikes.InjectingDateTime.UserServiceTests.UserServiceShouldCreateUserWithCorrectCreatedDate' failed:The solution is to inject a function that returns a DateTime:
Expected: 2015-05-28 09:08:18.824
But was: 2015-05-28 09:08:18.819
InjectingDateTime\InjectDateTimeDemo.cs(75,0): at Mike.Spikes.InjectingDateTime.UserServiceTests.UserServiceShouldCreateUserWithCorrectCreatedDate()
public class UserService : IUserServiceNow our unit test can rely on a fixed DateTime value rather than one that is changing as the test runs:
{
private readonly IUserData userData;
private readonly Func<datetime> now;
public UserService(IUserData userData, Func<datetime> now)
{
this.userData = userData;
this.now = now;
}
public void CreateUser(string username)
{
var user = new User(username, createdDateTime: now());
userData.SaveUser(user);
}
}
[TestFixture]And the test passes as expected.
public class UserServiceTests
{
private IUserService sut;
private IUserData userData;
// stub the system date as some arbirary date
private readonly DateTime now = new DateTime(2015, 5, 28, 10, 46, 33);
[SetUp]
public void SetUp()
{
userData = MockRepository.GenerateStub<iuserdata>();
sut = new UserService(userData, () => now);
}
[Test]
public void UserServiceShouldCreateUserWithCorrectCreatedDate()
{
User user = null;
userData.Stub(x => x.SaveUser(null)).IgnoreArguments().Callback<user>(x =>
{
user = x;
return true;
});
sut.CreateUser("mike");
Assert.AreEqual(now, user.CreatedDateTime);
}
}
In our composition root we inject the current system time (here as UTC):
var userService = new UserService(userData, () => DateTime.UtcNow);This pattern can be especially useful when we want to test business logic that relies on time passing. For example, say we want to check if an offer has expired; we can write unit tests for the case where the current (stubbed) time is both before and after the expiry time just by injecting different values into the system-under-test. Because we can stub the system time to be anything we want it to be, it makes it easy to test time based busines logic.
0 comments