Las week we discovered that some of our test would"randomly" fail depending of the time of the day. After investigating the issue we found that the culprit was that the service being tested was taking decisions based on the current system time (DateTime.Now) leading to different outcomes through the day. So how do we deal with time dependencies in tests?
My first thought was creating an IClock interface and injecting an implementation to the service. It seemed like an overkill and then I googled enough to find a short and very simple solution in Ayende’s 2008 article: Dealing with time in tests
I’ll use .NET Core and xUnit to illustrate the problem and the proposed solution.
Now let’s start:
Create a test project
Open a command prompt and run the following commands
1mkdir remove.datetime.dependency.test
2cd remove.datetime.dependency.test
3dotnet new -t xunittest
Create a SystemDateTime helper class
This is the helper class a proposed by Ayende
1namespace MyServices
2{
3 using System;
4
5 /// <summary>
6 /// Helper class that will return DateTime.Now, but can be changed to deal with tests.
7 /// https://ayende.com/blog/3408/dealing-with-time-in-tests
8 /// </summary>
9 public static class SystemDateTime
10 {
11
12 /// <summary>
13 /// Gets a function that when evaluated returns a local date and time.
14 /// </summary>
15 /// <returns>A function that when evaluated returns a local date and time.</returns>
16 public static Func<DateTime> Now = () => DateTime.Now;
17 }
18}
Create a Service with a dependency on time
The service will have two methods: one will use System.DateTime.Now and one will use the helper created on step 2
1namespace MyServices
2{
3 using System;
4
5 /// <summary>
6 /// A time dependent on DateTime.Now
7 /// </summary>
8 public class TimeDependentService
9 {
10
11 /// <summary>
12 /// Tells if it's morning.
13 /// </summary>
14 /// <returns>true if it's before 12</returns>
15 public bool OldIsMorning()
16 {
17 return DateTime.Now.Hour < 12;
18 }
19
20 /// <summary>
21 /// Tells if it's morning using the helper by Ayende.
22 /// </summary>
23 /// <returns>true if it's before 12</returns>
24 public bool IsMorning()
25 {
26 return SystemDateTime.Now().Hour < 12;
27 }
28 }
29}
Create the tests
Replace the content of Tests.cs with the following code
1namespace Tests
2{
3 using System;
4 using MyServices;
5 using Xunit;
6
7 public class Tests
8 {
9 /// <summary>
10 /// If this test is run after 12PM it will fail cause of the dependency on the system time.
11 /// </summary>
12 [Fact]
13 public void Will_Fail_After_Noon()
14 {
15 var svc = new TimeDependentService();
16 Assert.True(svc.OldIsMorning());
17 }
18
19 /// <summary>
20 /// This test will run OK no matter the systems time, cause we are using the Ayende
21 /// approach to tests with time dependencies.
22 /// </summary>
23 [Fact]
24 public void Will_Run_OK_No_Matter_The_Real_Time()
25 {
26 // With Ayende's helper we can for the time to 11 AM
27 SystemDateTime.Now = () => new DateTime(2016, 10, 10, 11, 0, 0);
28 var svc = new TimeDependentService();
29 Assert.True(svc.IsMorning());
30 }
31 }
32}
Run the tests
1dotnet test
No matter the system time the test: Will_Run_OK_No_Matter_The_Real_Time will always pass, while the Will_Fail_After_Noon test will fail any time after 12 PM.
You can get the code here: https://github.com/cmendible/dotnetcore.samples/tree/main/remove.datetime.dependency.test
Hope it helps!
Comments