How to Mock Static Method or Property in C#
Problem
In C#, it is difficult when we want to test a method that contains DateTime.Now because we cannot control its behaviour.
Considering below code, the value of DateTime.Now will depend on the time the code is executed.
So, when we test on today 10:15:00 AM, the expectation will be 10:16:00 AM. But, when this is executed on CI server later, the expectation won’t be 10:16:00 AM, which will fail our tests.
internal class Program
{
static void Main(string[] args)
{
TimeProvider timeProvider = new TimeProvider();
DateTime dateTimeNow = timeProvider.GetNextOneMinuteFromNow();
Console.WriteLine(dateTimeNow);
}
}
public class TimeProvider
{
public DateTime GetNextOneMinuteFromNow()
{
return DateTime.Now.AddMinutes(1);
}
}
Solution
There are 2 solutions we can use:
Create a wrapper class and apply dependency injection
Considering below code, we do these modifications:
- Add interface
IDateTimeNowProviderthat abstracts concrete class that will be injected toTimeProviderclass - Add class
DateTimeNowProvideras concrete implementation ofIDateTimeNowProviderinterface - Inject
DateTimeNowProvidertoTimeProviderclass
internal class Program
{
static void Main(string[] args)
{
TimeProvider timeProvider = new TimeProvider(new DateTimeNowProvider());
DateTime dateTimeNow = timeProvider.GetNextOneMinuteFromNow();
Console.WriteLine(dateTimeNow);
}
}
public interface IDateTimeNowProvider
{
DateTime DateTimeNow { get; }
}
public class DateTimeNowProvider : IDateTimeNowProvider
{
public DateTime DateTimeNow => DateTime.Now;
}
public class TimeProvider
{
public IDateTimeNowProvider DateTimeNowProvider { get; }
public TimeProvider(IDateTimeNowProvider dateTimeNowProvider)
{
DateTimeNowProvider = dateTimeNowProvider;
}
public DateTime GetNextOneMinuteFromNow()
{
return DateTimeNowProvider.DateTimeNow.AddMinutes(1);
}
}
We also add unit test showing how to test class that wraps DateTime.Now. We use Moq as mocking framework.
[TestMethod()]
public void Get_Next_Minute_From_Current_Time()
{
// Arrange
var dateTimeNowProvider = new Mock<IDateTimeNowProvider>();
dateTimeNowProvider.Setup(x => x.DateTimeNow).Returns(new DateTime(2022, 6, 12, 11, 15, 0));
TimeProvider timeProvider = new TimeProvider(dateTimeNowProvider.Object);
// Act
DateTime actual = timeProvider.GetNextOneMinuteFromNow();
// Assert
DateTime exptected = new DateTime(2022, 6, 12, 11, 16, 0);
Assert.AreEqual(exptected, actual);
}
Use Shims as part of Microsoft Fakes Framework
Microsoft Fakes has caveat that it's only available in Visual Studio Enterprise. Other than that, you cannot use Shims, e.g., Visual Studio Professional, Community, JetBrains Rider, etc.
We need to do these in order to use Shims:
Add fakes assembly of
System.RuntimewhereDateTimeclass/struct resides.
This applies for .NET 6.0. For previous version, typicallyDateTimeresides onmscorlib.
Modify
System.Runtime.fakesfile that is generated after we add fakes assembly above<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/"> <Assembly Name="System.Runtime" Version="6.0.0.0"/> <StubGeneration> <Clear/> </StubGeneration> <ShimGeneration> <Clear/> <Add FullName="System.DateTime"/> </ShimGeneration> </Fakes>Add fakes unit test using
ShimsWe must prefix the class that we shim with[TestMethod()] public void Get_Next_Minute_From_Current_Time() { using (ShimsContext.Create()) { // Arrange System.Fakes.ShimDateTime.NowGet = () => { return new DateTime(2022, 6, 12, 11, 15, 0); }; // Act TimeProvider timeProvider = new TimeProvider(); DateTime actual = timeProvider.GetNextOneMinuteFromNow(); // Assert DateTime expected = new DateTime(2022, 6, 12, 11, 16, 0); Assert.AreEqual(expected, actual); } }Shim, e.g.,ShimDateTime. For static getter property, we append the name of the propertyNowwithGetto beNowGet.
The property or method that we shim must also be insideShimsContextandusingstatement.
So that when it goes out of the scope, the Shim behaviour is removed. Otherwise,DateTime.Nowvalue is always the same in every test we create.
Source Code
The source can be found in the github.
