In this post, we will see how to manage correctly a Date type variable.
Many times in our code, when we have to define a variable with the current date and time, we use Datetime.Now… or at least, this is what I do…..
In theory this is correct but, if we have to set up something based on this value, it may be impossible to create Unit Tests.
We start creating a Console application called TestDate where we will add a class called Core, so defined:
[CORE.CS]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | namespace TestDate; public class Core { public string Greeting() { DateTime today = DateTime.Now; if (today.Hour < 12) return "Good Morning" ; if (today.Hour < 19) return "Good Afternoon" ; return "Good Evening" ; } } |
Then, we add a xUnit project called UnitTest (I have a lot of imagination…) where we will create a class called UnitTestGreeting, so defined:
[UNITTESTGREETING.CS]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | using Moq; using System; using TestDate; using Xunit; namespace UnitTest; public class UnitTestGreeting { const string GreetingMornig = "Good Morning" ; const string GreetingAfternoon = "Good Afternoon" ; const string GreetingEvening = "Good Evening" ; private readonly Core objCore; public UnitTestGreeting() { objCore = new Core(); } [Fact] public void TestCore_WhenDateLessThan12_ShouldReturnGoodMorning() { // Arrange string result = string .Empty; // Act result = objCore.Greeting(); // Assert Assert.Equal(GreetingMornig, result); } [Fact] public void TestCore_WhenDateLessThan19_ShouldReturnGoodAfternoon() { // Arrange string result = string .Empty; // Act result = objCore.Greeting(); // Assert Assert.Equal(GreetingAfternoon, result); } [Fact] public void TestCore_WhenDateGreaterThan19_ShouldReturnGoodEvening() { // Arrange string result = string .Empty; // Act result = objCore.Greeting(); // Assert Assert.Equal(GreetingEvening, result); } } |
We can see that these tests depend of when we run them.
In fact, if I run now (it is 00:08 am) the tests, the only test that will pass is the first:
The problem is that we cannot modify the Date because, it is a value internal at the method Greeting().
How can we fix it?
It is very easy…
We create a new interface called IServiceDate where we will define a method called GetDate(), used to get the current datetime:
[ISERVICEDATE.CS]
1 2 3 4 5 6 | namespace TestDate; public interface IServiceDate { public DateTime GetDate(); } |
Then, we create a class called ServiceDate where we will implement the GetDate method:
[SERVICEDATE.CS]
1 2 3 4 5 6 7 8 9 | namespace TestDate; public class ServiceDate: IServiceDate { public DateTime GetDate() { return DateTime.Now; } } |
Finally, we modify the class Core:
[CORE.CS]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | namespace TestDate; public class Core { private readonly IServiceDate _serviceDate; public Core(IServiceDate serviceDate) { _serviceDate = serviceDate; } public string Greeting() { DateTime today = _serviceDate.GetDate(); if (today.Hour < 12) return "Good Morning" ; if (today.Hour < 19) return "Good Afternoon" ; return "Good Evening" ; } } |
In this way we have a more maintainable code and we can use Moq to set up the return value of GetDate(), in order to run the tests with correct values:
[UNITTESTGREETING.CS]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | using Moq; using System; using TestDate; using Xunit; namespace UnitTest; public class UnitTestGreeting { const string GreetingMornig = "Good Morning" ; const string GreetingAfternoon = "Good Afternoon" ; const string GreetingEvening = "Good Evening" ; private readonly Core objCore; private readonly Mock<IServiceDate> objServiceDate; public UnitTestGreeting() { objServiceDate = new Mock<IServiceDate>(); objCore = new Core(objServiceDate.Object); } [Fact] public void TestCore_WhenDateLessThan12_ShouldReturnGoodMorning() { // Arrange string result = string .Empty; DateTime today = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 11, 00, 00); objServiceDate.Setup(setup => setup.GetDate()).Returns(today); // Act result = objCore.Greeting(); // Assert Assert.Equal(GreetingMornig, result); } [Fact] public void TestCore_WhenDateLessThan19_ShouldReturnGoodAfternoon() { // Arrange string result = string .Empty; DateTime today = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 18, 00, 00); objServiceDate.Setup(setup => setup.GetDate()).Returns(today); // Act result = objCore.Greeting(); // Assert Assert.Equal(GreetingAfternoon, result); } [Fact] public void TestCore_WhenDateGreaterThan19_ShouldReturnGoodEvening() { // Arrange string result = string .Empty; DateTime today = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 22, 00, 00); objServiceDate.Setup(setup => setup.GetDate()).Returns(today); // Act result = objCore.Greeting(); // Assert Assert.Equal(GreetingEvening, result); } } |
Now, if we run the tests again, these will be the results: