I have a client running on an "East US" Azure server. Some of the code that works okay in development (on UK server) does not on that server (East US server). I believe the issue is due to me converting a date string into a UTC date time but i would like to write a test for it to indeed prove i have solved the issue.
Is there a way to fake the fact my unit test is running in a different time zone?
For example, DateTime.Now should return the time in East US rather then UK.
Is this possible?
Yes, you can fake the timezone in which your unit tests are running.
Here's a simple class that changes the local timezone to the timeZoneInfo
provided in the constructor and resets the original local timezone when disposed.
using System;
using ReflectionMagic;
namespace TestProject
{
public class FakeLocalTimeZone : IDisposable
{
private readonly TimeZoneInfo _actualLocalTimeZoneInfo;
private static void SetLocalTimeZone(TimeZoneInfo timeZoneInfo)
{
typeof(TimeZoneInfo).AsDynamicType().s_cachedData._localTimeZone = timeZoneInfo;
}
public FakeLocalTimeZone(TimeZoneInfo timeZoneInfo)
{
_actualLocalTimeZoneInfo = TimeZoneInfo.Local;
SetLocalTimeZone(timeZoneInfo);
}
public void Dispose()
{
SetLocalTimeZone(_actualLocalTimeZoneInfo);
}
}
}
The FakeLocalTimeZone
class is using ReflectionMagic to access private fields (which are protected by a lock), so don't use this in production code, only in your unit tests!
Here is how you can use it:
using System;
using Xunit;
namespace TestProject
{
public class UnitTest
{
[Fact]
public void TestFakeLocalTimeZone()
{
using (new FakeLocalTimeZone(TimeZoneInfo.FindSystemTimeZoneById("US/Eastern")))
{
// In this scope, the local time zone is US/Eastern
// Here, DateTime.Now returns 2020-09-02T02:58:46
Assert.Equal("US/Eastern", TimeZoneInfo.Local.Id);
Assert.Equal(TimeSpan.FromHours(-5), TimeZoneInfo.Local.BaseUtcOffset);
}
// In this scope (i.e. after the FakeLocalTimeZone is disposed) the local time zone is the one of the computer.
// It is not safe to assume anything about which is the local time zone here.
// Here, DateTime.Now returns 2020-09-02T08:58:46 (my computer is in the Europe/Zurich time zone)
}
}
}
This answers how to fake the fact my unit test is running in a different time zone.
Now, as user3292642 suggested in the comments, a better design would be to use an interface and not call DateTime.Now
directly in your code so that you can provide a fake now in your unit tests.
And an even better choice would be to use Noda Time instead of the DateTime
type. Noda Time has all the abstractions and types to properly work with date and time. Even if you don't plan to use it, you should read its user guide, you will learn a lot.
On our version .netcoreapp 3.1
the answer from 0xced didn't work because the internal api looks like it changed slightly. I made some small adjustments to make it work there.
public class FakeLocalTimeZone : IDisposable
{
private readonly TimeZoneInfo _actualLocalTimeZoneInfo;
private static void SetLocalTimeZone(TimeZoneInfo timeZoneInfo)
{
var info = typeof(TimeZoneInfo).GetField("s_cachedData", BindingFlags.NonPublic | BindingFlags.Static);
object cachedData = info.GetValue(null);
var field = cachedData.GetType().GetField("_localTimeZone", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Instance);
field.SetValue(cachedData, timeZoneInfo);
}
public FakeLocalTimeZone(TimeZoneInfo timeZoneInfo)
{
_actualLocalTimeZoneInfo = TimeZoneInfo.Local;
SetLocalTimeZone(timeZoneInfo);
}
public void Dispose()
{
SetLocalTimeZone(_actualLocalTimeZoneInfo);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With