Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Generate wrapper interfaces and DI registration for hard-to-test static dependencies in C#, when the abstraction does NOT exist yet. Produces IFileSystem, IEnvironmentProvider, IConsole, IProcessRunner wrappers, or guides first-time adoption of TimeProvider and IHttpClientFactory and registering them in DI. 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, set up
.claude/skills/managedcode-generate-testability-wrappers/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 41% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 47% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 16% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 228% | 0% |
| case-11 | ✓→✗ | ▼ Worse | 59% | 0% |
Generate wrapper interfaces, default implementations, and DI service registration code for untestable static dependencies. For statics that already have .NET built-in abstractions (TimeProvider, IHttpClientFactory), guide adoption of the built-in. For statics without built-in alternatives, generate custom minimal wrappers.
detect-static-dependencies and identifying which statics to wrapTimeProvider (.NET 8+) or System.IO.AbstractionsEnvironment.*, Console.*, or Process.*detect-static-dependencies)migrate-static-to-wrapper)| Input | Required | Description | |-------|----------|-------------| | Static category | Yes | Which category: time, filesystem, environment, network, console, process | | Target framework | Yes | The TargetFramework from .csproj (affects which built-in abstractions exist) | | DI container | No | Which DI framework: microsoft (default), autofac, none (ambient context) | | Namespace | No | Target namespace for generated wrapper code |
Based on the category and target framework:
| Category | .NET 8+ | .NET 6-7 | .NET Framework | |----------|---------|----------|----------------| | Time | TimeProvider (built-in) | TimeProvider via Microsoft.Bcl.TimeProvider NuGet | Custom ISystemClock | | File system | System.IO.Abstractions (NuGet) | Same | Same | | HTTP | IHttpClientFactory (built-in) | Same | Same | | Environment | Custom IEnvironmentProvider | Same | Same | | Console | Custom IConsole | Same | Same | | Process | Custom IProcessRunner | Same | Same |
No wrapper code needed — guide the user:
csharpbuilder.Services.AddSingleton(TimeProvider.System);
csharppublic class OrderProcessor(TimeProvider timeProvider) { public bool IsExpired(Order order) => timeProvider.GetUtcNow() > order.ExpiresAt; }
FakeTimeProvider:csharp// 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));
Guide: install Microsoft.Bcl.TimeProvider NuGet. Same API as above.
No wrapper code needed — register typed clients via builder.Services.AddHttpClient<MyService>() and inject HttpClient directly into the class constructor.
For categories without built-in abstractions, follow this template:
Only include methods that were actually detected in the codebase. Do NOT generate a wrapper for every possible member — wrap only what is used.
csharpnamespace <Namespace>; /// <summary> /// Abstraction over <static class> for testability. /// </summary> public interface I<WrapperName> { // One method per detected static call <return type> <MethodName>(<parameters>); }
csharpnamespace <Namespace>; /// <summary> /// Default implementation that delegates to <static class>. /// </summary> public sealed class <WrapperName> : I<WrapperName> { public <return type> <MethodName>(<parameters>) => <StaticClass>.<Method>(<arguments>); }
csharp// In Program.cs or Startup.cs: builder.Services.AddSingleton<I<WrapperName>, <WrapperName>>();
Prefer the established System.IO.Abstractions NuGet package over custom wrappers:
dotnet add package System.IO.Abstractionscsharpbuilder.Services.AddSingleton<IFileSystem, FileSystem>();
IFileSystem into classes:csharppublic class ConfigLoader(IFileSystem fileSystem) { public string LoadConfig(string path) => fileSystem.File.ReadAllText(path); }
MockFileSystem:dotnet add <TestProject> package System.IO.Abstractions.TestingHelperscsharpvar 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"));
If the codebase does not use DI (e.g., old console app, library code), offer the ambient context pattern:
csharppublic 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; } }
Key trade-offs: AsyncLocal<T> ensures parallel tests don't interfere; production cost is one null check per call; the static readonly field is essentially free.
Generate files following the project's existing conventions:
Abstractions/ or Interfaces/ folder, place the interface thereInfrastructure/ or Services/ folder, place the implementation thereAlways generate:
AddSingleton for stateless wrappers, AddTransient for stateful onesTimeProvider is recommended over custom ISystemClockAsyncLocal<T>, scoped disposal, and trade-off explanation| 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 TimeProvider instead | | Custom file system wrapper | Prefer System.IO.Abstractions NuGet — battle-tested, complete | | Registering scoped when singleton suffices | Stateless wrappers should be AddSingleton | | Forgetting test helper packages | Microsoft.Extensions.TimeProvider.Testing for time, System.IO.Abstractions.TestingHelpers for filesystem | | Ambient context without AsyncLocal | Non-async [ThreadStatic] breaks with async/await — always use AsyncLocal<T> |
Other measured skills in the registry, with their headline benchmark lift.