Loading...
Loading...
Generate wrapper interfaces and DI registration for hard-to-test static dependencies in C#. Produces IFileSystem, IEnvironmentProvider, IConsole, IProcessRunner wrappers, or guides adoption of TimeProvider and IHttpClientFactory. USE FOR: generate wrapper for static, create IFileSystem wrapper, wrap DateTime.Now, make static testable, make class testable, create abstraction for File.*, generate DI registration, TimeProvider adoption, IHttpClientFactory setup, testability wrapper, mock-friendly interface, mock time in tests, create the right abstraction to mock, how to mock DateTime, test code using File.ReadAllText, what abstraction for Environment, how to make statics injectable, adopt System.IO.Abstractions, make file calls testable. DO NOT USE FOR: detecting statics (use detect-static-dependencies), migrating call sites (use migrate-static-to-wrapper), general interface design not about testability.
npx skill4agent add dotnet/skills generate-testability-wrappersTimeProviderIHttpClientFactorydetect-static-dependenciesTimeProviderSystem.IO.AbstractionsEnvironment.*Console.*Process.*detect-static-dependenciesmigrate-static-to-wrapper| Input | Required | Description |
|---|---|---|
| Static category | Yes | Which category: |
| Target framework | Yes | The |
| DI container | No | Which DI framework: |
| Namespace | No | Target namespace for generated wrapper code |
| Category | .NET 8+ | .NET 6-7 | .NET Framework |
|---|---|---|---|
| Time | | | Custom |
| File system | | Same | Same |
| HTTP | | Same | Same |
| Environment | Custom | Same | Same |
| Console | Custom | Same | Same |
| Process | Custom | Same | Same |
builder.Services.AddSingleton(TimeProvider.System);public class OrderProcessor(TimeProvider timeProvider)
{
public bool IsExpired(Order order)
=> timeProvider.GetUtcNow() > order.ExpiresAt;
}FakeTimeProvider// Requires Microsoft.Extensions.TimeProvider.Testing NuGet
var fakeTime = new FakeTimeProvider(new DateTimeOffset(2026, 1, 15, 0, 0, 0, TimeSpan.Zero));
var processor = new OrderProcessor(fakeTime);
fakeTime.Advance(TimeSpan.FromDays(1));
Assert.True(processor.IsExpired(order));Microsoft.Bcl.TimeProviderbuilder.Services.AddHttpClient<MyService>()HttpClientnamespace <Namespace>;
/// <summary>
/// Abstraction over <static class> for testability.
/// </summary>
public interface I<WrapperName>
{
// One method per detected static call
<return type> <MethodName>(<parameters>);
}namespace <Namespace>;
/// <summary>
/// Default implementation that delegates to <static class>.
/// </summary>
public sealed class <WrapperName> : I<WrapperName>
{
public <return type> <MethodName>(<parameters>)
=> <StaticClass>.<Method>(<arguments>);
}// In Program.cs or Startup.cs:
builder.Services.AddSingleton<I<WrapperName>, <WrapperName>>();System.IO.Abstractionsdotnet add package System.IO.Abstractionsbuilder.Services.AddSingleton<IFileSystem, FileSystem>();IFileSystempublic class ConfigLoader(IFileSystem fileSystem)
{
public string LoadConfig(string path)
=> fileSystem.File.ReadAllText(path);
}MockFileSystemdotnet add <TestProject> package System.IO.Abstractions.TestingHelpersvar mockFs = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ "/config.json", new MockFileData("{\"key\": \"value\"}") }
});
var loader = new ConfigLoader(mockFs);
Assert.Equal("{\"key\": \"value\"}", loader.LoadConfig("/config.json"));public static class Clock
{
private static readonly AsyncLocal<Func<DateTimeOffset>?> s_override = new();
public static DateTimeOffset UtcNow
=> s_override.Value?.Invoke() ?? TimeProvider.System.GetUtcNow();
public static IDisposable Override(DateTimeOffset fixedTime)
{
s_override.Value = () => fixedTime;
return new Scope();
}
private sealed class Scope : IDisposable
{
public void Dispose() => s_override.Value = null;
}
}AsyncLocal<T>static readonlyAbstractions/Interfaces/Infrastructure/Services/AddSingletonAddTransientTimeProviderISystemClockAsyncLocal<T>| Pitfall | Solution |
|---|---|
| Wrapping ALL members of a static class | Only wrap methods actually called in the codebase |
| Custom time wrapper on .NET 8+ | Use built-in |
| Custom file system wrapper | Prefer |
| Registering scoped when singleton suffices | Stateless wrappers should be |
| Forgetting test helper packages | |
Ambient context without | Non-async |