So often we have to use DateTime.UtcNow or DateTime.Now in our code. To make it easier to mock current date time in unit test and to make unit tests more resilient, it’s better to not use DateTime.UtcNow or DateTime.Now directly but rather pass it in or make it as a property.
Let’s say we have a POCO object like this
public class ToDoItem { public Guid Id { get; set; } public string Title { get; set; } public string Content { get; set; } public bool IsActive { get; set; } public DateTime CreatedDate { get; set; } public DateTime? UpdatedDate { get; set; } }
Now, say we have a DTO object like below for creating a new Article with a method ToPoco
public class CreateToDoItemDto { public Guid Id { get; set; } public string Title { get; set; } public string Content { get; set; } public ToDoItem ToPoco() { return new ToDoItem { Id = Id, Title = Title, Content = Content, IsActive = true, CreatedDate = DateTime.UtcNow }; } }
Doing it like above means you can’t unit test what the value set to CreatedDate will be.
[Test] public void ToPoco_Should_Return_Correct_Result() { // Arrange var createToDoItemDto = new CreateToDoItemDto { Id = Guid.NewGuid(), Title = Guid.NewGuid().ToString(), Content = Guid.NewGuid().ToString() }; // Act var result = createToDoItemDto.ToPoco(); // Assert result.Id.Should().Be(createToDoItemDto.Id); result.Title.Should().Be(createToDoItemDto.Title); result.Content.Should().Be(createToDoItemDto.Content); result.IsActive.Should().BeTrue(); // cannot verify this result.CreatedDate.Should().Be(); }
To make this unit testable, there are a couple ways that can be done
- Passing the current date as a parameter into the method
public class CreateToDoItemDto { public Guid Id { get; set; } public string Title { get; set; } public string Content { get; set; } public ToDoItem ToPoco(DateTime currentDate) { return new ToDoItem { Id = Id, Title = Title, Content = Content, IsActive = true, CreatedDate = currentDate }; } }
Then the test becomes
[Test] public void ToPoco_Should_Return_Correct_Result() { // Arrange var createdDate = DateTime.UtcNow; var createToDoItemDto = new CreateToDoItemDto { Id = Guid.NewGuid(), Title = Guid.NewGuid().ToString(), Content = Guid.NewGuid().ToString(), }; // Act var result = createToDoItemDto.ToPoco(createdDate); // Assert result.Id.Should().Be(createToDoItemDto.Id); result.Title.Should().Be(createToDoItemDto.Title); result.Content.Should().Be(createToDoItemDto.Content); result.IsActive.Should().BeTrue(); result.CreatedDate.Should().Be(createdDate); }
- Adding a delegate property that returns the current date time. I prefer this method as we don’t have to pass in the current date time whenever we use ToPoco(). Also, if your class implements IValidatableObject for validation and you need to validate your object against current date time, you need to use this method since you cannot pass additional parameter into the Validate() method.
public class CreateToDoItemDto { public Guid Id { get; set; } public string Title { get; set; } public string Content { get; set; } public Func<DateTime> UtcNow { get; set; } = () => DateTime.UtcNow; public ToDoItem ToPoco() { return new ToDoItem { Id = Id, Title = Title, Content = Content, IsActive = true, CreatedDate = UtcNow() }; } }
Then the test becomes
[Test] public void ToPoco_Should_Return_Correct_Result() { // Arrange var createdDate = DateTime.UtcNow; var createToDoItemDto = new CreateToDoItemDto { Id = Guid.NewGuid(), Title = Guid.NewGuid().ToString(), Content = Guid.NewGuid().ToString(), UtcNow = () => createdDate }; // Act var result = createToDoItemDto.ToPoco(); // Assert result.Id.Should().Be(createToDoItemDto.Id); result.Title.Should().Be(createToDoItemDto.Title); result.Content.Should().Be(createToDoItemDto.Content); result.IsActive.Should().BeTrue(); result.CreatedDate.Should().Be(createdDate); }