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
IDateTimeNowProvider
that abstracts concrete class that will be injected toTimeProvider
class - Add class
DateTimeNowProvider
as concrete implementation ofIDateTimeNowProvider
interface - Inject
DateTimeNowProvider
toTimeProvider
class
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.Runtime
whereDateTime
class/struct resides.
This applies for .NET 6.0. For previous version, typicallyDateTime
resides onmscorlib
.Modify
System.Runtime.fakes
file 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
Shims
We 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 propertyNow
withGet
to beNowGet
.
The property or method that we shim must also be insideShimsContext
andusing
statement.
So that when it goes out of the scope, the Shim behaviour is removed. Otherwise,DateTime.Now
value is always the same in every test we create.
Source Code
The source can be found in the github.