PatchStatic 1.0.0
dotnet add package PatchStatic --version 1.0.0
NuGet\Install-Package PatchStatic -Version 1.0.0
<PackageReference Include="PatchStatic" Version="1.0.0" />
<PackageVersion Include="PatchStatic" Version="1.0.0" />
<PackageReference Include="PatchStatic" />
paket add PatchStatic --version 1.0.0
#r "nuget: PatchStatic, 1.0.0"
#:package PatchStatic@1.0.0
#addin nuget:?package=PatchStatic&version=1.0.0
#tool nuget:?package=PatchStatic&version=1.0.0
PatchStatic User Guide
PatchStatic is a C# library that enables mocking of static methods and properties for unit testing purposes. It uses Harmony to patch static methods at runtime, providing thread-safe mocking with automatic cleanup through scoped mocking patterns.
Table of Contents
Key Features
- Static Method Mocking: Mock static methods and properties
- Thread-Safe: Each thread maintains isolated mock state
- Scoped Mocking: Automatic cleanup when scope is disposed
- Property Support: Mock static properties through their getter methods
- Global Reset: Ability to clear all patches globally
- Expression-Based: Type-safe method selection using lambda expressions
Basic Usage
Simple Method Mocking
The library includes a sample StaticTimeProvider class that demonstrates typical static method patterns:
public static class StaticTimeProvider
{
public static DateTime Now => DateTime.Now;
public static bool IsBusinessHours()
{
var hour = Now.Hour;
return hour is >= 9 and < 17;
}
}
Here's how to mock it in your tests:
[Fact]
public void SimpleMethodMocking_IsBusinessHours_ReturnsMockedValue()
{
Mock.Scope(scope =>
{
// Mock the static property to return a specific time
var mockNow = scope.Static(() => StaticTimeProvider.Now);
mockNow.Returns(new DateTime(2025, 6, 9, 10, 0, 0));
// Test the business logic that depends on the mocked time
Assert.True(StaticTimeProvider.IsBusinessHours());
});
// Outside the scope, the original behavior is restored
var realNow = StaticTimeProvider.Now;
Assert.IsType<DateTime>(realNow);
}
Property Mocking
You can mock static properties directly:
[Fact]
public void PropertyMocking_NowReturnsMockedDateTime()
{
Mock.Scope(scope =>
{
var mockNow = scope.Static(() => StaticTimeProvider.Now);
var fakeTime = new DateTime(2025, 6, 9, 20, 0, 0);
mockNow.Returns(fakeTime);
// The mocked value is returned
Assert.Equal(fakeTime, StaticTimeProvider.Now);
});
// After scope disposal, original behavior is restored
Assert.NotEqual(new DateTime(2025, 6, 9, 20, 0, 0), StaticTimeProvider.Now);
}
Advanced Features
Multiple Mocks in One Scope
You can mock multiple static methods within the same scope:
[Fact]
public void MultipleMocksInOneScope_WorkCorrectly()
{
Mock.Scope(scope =>
{
// Mock the Now property
var mockNow = scope.Static(() => StaticTimeProvider.Now);
mockNow.Returns(new DateTime(2025, 6, 9, 11, 0, 0));
// Mock the IsBusinessHours method directly
var mockIsBusinessHours = scope.Static(() => StaticTimeProvider.IsBusinessHours());
mockIsBusinessHours.Returns(false);
// Both mocks are active
Assert.Equal(new DateTime(2025, 6, 9, 11, 0, 0), StaticTimeProvider.Now);
Assert.False(StaticTimeProvider.IsBusinessHours());
});
// All mocks are cleaned up automatically
Assert.IsType<DateTime>(StaticTimeProvider.Now);
}
Thread Isolation
Each thread maintains its own mock state, ensuring thread safety:
[Fact]
public void BasicThreadIsolation_MocksAreThreadSpecific()
{
var mainThreadId = Environment.CurrentManagedThreadId;
var otherThreadId = -1;
Mock.Scope(scope =>
{
// Mock only affects the current thread
var mockNow = scope.Static(() => StaticTimeProvider.Now);
mockNow.Returns(new DateTime(2025, 6, 9, 10, 0, 0));
Assert.True(StaticTimeProvider.IsBusinessHours());
// Other threads see the original behavior
var task = Task.Run(() =>
{
otherThreadId = Environment.CurrentManagedThreadId;
return StaticTimeProvider.IsBusinessHours();
});
var otherResult = task.Result;
// Verify threads are different
Assert.NotEqual(mainThreadId, otherThreadId);
// Other thread result is based on actual time, not mocked time
Assert.True(otherResult || !otherResult);
});
}
Concurrent Testing with Parallel.For
Test concurrent scenarios using Parallel.For:
[Fact]
public void RaceConditionTesting_DifferentMocksInEachThread()
{
var exceptions = new System.Collections.Concurrent.ConcurrentQueue<Exception>();
Parallel.For(0, 10, i =>
{
try
{
Mock.Scope(scope =>
{
// Each thread gets a different mocked time
var mockedTime = new DateTime(2025, 6, 9, 10, 0, 0).AddHours(i);
var mockNow = scope.Static(() => StaticTimeProvider.Now);
mockNow.Returns(mockedTime);
var now = StaticTimeProvider.Now;
Assert.Equal(mockedTime, now);
});
}
catch (Exception ex)
{
exceptions.Enqueue(ex);
}
});
// Verify no exceptions occurred
Assert.Empty(exceptions);
}
Concurrent Testing with Async Tasks
Test concurrent scenarios using async tasks:
[Fact]
public async Task RaceConditionTesting_DifferentMocksInEachThread_UsingAsyncTasks()
{
var exceptions = new System.Collections.Concurrent.ConcurrentQueue<Exception>();
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
{
var localIndex = i;
var task = Task.Run(() =>
{
try
{
Mock.Scope(scope =>
{
// Each task gets its own mocked time
var mockedTime = new DateTime(2025, 6, 9, 10, 0, 0).AddHours(localIndex);
var mockNow = scope.Static(() => StaticTimeProvider.Now);
mockNow.Returns(mockedTime);
var now = StaticTimeProvider.Now;
Assert.Equal(mockedTime, now);
});
}
catch (Exception ex)
{
exceptions.Enqueue(ex);
}
});
tasks.Add(task);
}
await Task.WhenAll(tasks);
// Verify no exceptions occurred in any task
Assert.Empty(exceptions);
}
API Reference
Mock Class
public static class Mock
{
// Create a new mock scope
public static void Scope(Action<MockScope> action)
// Global cleanup - removes all patches (use sparingly)
public static void GlobalResetAll()
}
MockScope Class
public class MockScope : IDisposable
{
// Create a mock for a static method or property
public StaticMethodMock<TResult> Static<TResult>(Expression<Func<TResult>> methodExpr)
}
StaticMethodMock<TResult> Class
public class StaticMethodMock<TResult>
{
// Set the return value for the mocked method
public StaticMethodMock<TResult> Returns(TResult value)
}
It Class (Parameter Matching)
public static class It
{
// Match any parameter of type T
public static T? IsAny<T>() => default;
}
Limitations
- Can only mock public static methods and properties
- Cannot patch readonly static methods or properties
- Does not support nested scopes (scope within scope)
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Lib.Harmony (>= 2.3.6)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|