/generate-testability-wrappers
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
$ npx -y skills add dotnet/skills --skill generate-testability-wrappers --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/generate-testability-wrappers
Context preview
The summary Claude sees to decide when to auto-load this skill.
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
SKILL.md
generate-testability-wrappers.SKILL.mdname: generate-testability-wrappers
description: >
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.
With no DI container, produces the ambient context seam instead.
USE FOR: generate wrapper for static, create IFileSystem wrapper, wrap DateTime.Now,
make a static or a class testable, create abstraction for File.*, generate DI registration,
adopt TimeProvider when it is not registered yet, IHttpClientFactory setup, testability
wrapper, how to make statics injectable, adopt System.IO.Abstractions, make code testable
without adding a DI framework.
DO NOT USE FOR: detecting statics (use detect-static-dependencies), migrating
call sites or replacing existing DateTime.*/File.* usages once the wrapper is created
or already registered in DI (use migrate-static-to-wrapper), general interface design.
license: MIT
Generate Testability Wrappers
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.
When to Use
- After running `detect-static-dependencies` and identifying which statics to wrap
- When the user asks to make a class testable by replacing statics with injected abstractions
- When adopting `TimeProvider` (.NET 8+) or `System.IO.Abstractions`
- When creating a custom wrapper for `Environment.*`, `Console.*`, or `Process.*`
- When there is no DI container and the seam has to be ambient rather than injected
When Not to Use
- The user wants to find statics first (use `detect-static-dependencies`)
- The user wants to bulk-replace call sites (use `migrate-static-to-wrapper`)
- The static is already behind an interface
> A project with **no DI container**, or a user who does not want to add one, is **not** a reason to skip this skill — > that is exactly what the ambient context seam in Step 5 is for. Choose the seam over constructor injection in that > case; do not decline the request and do not propose registering anything in a service collection.
Inputs
| 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 |
Workflow
Step 1: Determine the abstraction strategy
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 |
The table picks *which abstraction*. How it reaches the code under test is a separate axis: constructor injection when a DI container exists, and the **ambient context seam of Step 5** when one does not. Decide that axis first — check for a host builder, `IServiceCollection`, or an existing container registration — because a static class cannot take a constructor and a project without a container has nowhere to register anything. In that case skip Steps 2–4 and go to Step 5; the abstraction chosen above still applies, it is just reached through the ambient seam.
Step 2: Generate built-in abstraction adoption (Time, HTTP)
TimeProvider (.NET 8+)
No wrapper code needed — guide the user:
1. Register in DI:
builder.Services.AddSingleton(TimeProvider.System);
2. Inject into classes:
public class OrderProcessor(TimeProvider timeProvider)
{
public bool IsExpired(Order order)
=> timeProvider.GetUtcNow() > order.ExpiresAt;
}3. Test with `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));
TimeProvider (pre-.NET 8)
Guide: install `Microsoft.Bcl.TimeProvider` NuGet. Same API as above.
IHttpClientFactory
No wrapper code needed — register typed clients via `builder.Services.AddHttpClient<MyService>()` and inject `HttpClient` directly into the class constructor.
Step 3: Generate custom wrappers (Environment, Console, Process)
For categories without built-in abstractions, follow this template:
Interface — define the minimal surface
Only include methods that were actually detected in the codebase. Do NOT generate a wrapper for every possible member — wrap only what is used.
namespace <Namespace>;
/// <summary>
/// Abstraction over <static class> for testability.
/// </summary>
public interface I<WrapperName>
{
// One method per detected static call
<return type> <MethodName>(<parameters>);
}Default implementation — delegate to the real static
namespace <Namespace>;
/// <summary>
/// Default implementation that delegates to <static class>.
/// </summary>
public sealed class <WrapperName> : I<WrapperName>
{
public <return type> <MethodName>(<parameters>)
=> <StaticClass>.<MRead more
name: generate-testability-wrappers description: > 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. With no DI container, produces the ambient context seam instead. USE FOR: generate wrapper for static, create IFileSystem wrapper, wrap DateTime.Now, make a static or a class testable, create abstraction for File.*, generate DI registration, adopt TimeProvider when it is not registered yet, IHttpClientFactory setup, testability wrapper, how to make statics injectable, adopt System.IO.Abstractions, make code testable without adding a DI framework. DO NOT USE FOR: detecting statics (use detect-static-dependencies), migrating call sites or replacing existing DateTime.*/File.* usages once the wrapper is created or already registered in DI (use migrate-static-to-wrapper), general interface design. license: MIT
Generate Testability Wrappers
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.
When to Use
- After running `detect-static-dependencies` and identifying which statics to wrap
- When the user asks to make a class testable by replacing statics with injected abstractions
- When adopting `TimeProvider` (.NET 8+) or `System.IO.Abstractions`
- When creating a custom wrapper for `Environment.*`, `Console.*`, or `Process.*`
- When there is no DI container and the seam has to be ambient rather than injected
When Not to Use
- The user wants to find statics first (use `detect-static-dependencies`)
- The user wants to bulk-replace call sites (use `migrate-static-to-wrapper`)
- The static is already behind an interface
> A project with **no DI container**, or a user who does not want to add one, is **not** a reason to skip this skill — > that is exactly what the ambient context seam in Step 5 is for. Choose the seam over constructor injection in that > case; do not decline the request and do not propose registering anything in a service collection.
Inputs
| 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 |
Workflow
Step 1: Determine the abstraction strategy
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 |
The table picks *which abstraction*. How it reaches the code under test is a separate axis: constructor injection when a DI container exists, and the **ambient context seam of Step 5** when one does not. Decide that axis first — check for a host builder, `IServiceCollection`, or an existing container registration — because a static class cannot take a constructor and a project without a container has nowhere to register anything. In that case skip Steps 2–4 and go to Step 5; the abstraction chosen above still applies, it is just reached through the ambient seam.
Step 2: Generate built-in abstraction adoption (Time, HTTP)
TimeProvider (.NET 8+)
No wrapper code needed — guide the user:
1. Register in DI:
builder.Services.AddSingleton(TimeProvider.System);
2. Inject into classes:
public class OrderProcessor(TimeProvider timeProvider)
{
public bool IsExpired(Order order)
=> timeProvider.GetUtcNow() > order.ExpiresAt;
}3. Test with `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));
TimeProvider (pre-.NET 8)
Guide: install `Microsoft.Bcl.TimeProvider` NuGet. Same API as above.
IHttpClientFactory
No wrapper code needed — register typed clients via `builder.Services.AddHttpClient<MyService>()` and inject `HttpClient` directly into the class constructor.
Step 3: Generate custom wrappers (Environment, Console, Process)
For categories without built-in abstractions, follow this template:
Interface — define the minimal surface
Only include methods that were actually detected in the codebase. Do NOT generate a wrapper for every possible member — wrap only what is used.
namespace <Namespace>;
/// <summary>
/// Abstraction over <static class> for testability.
/// </summary>
public interface I<WrapperName>
{
// One method per detected static call
<return type> <MethodName>(<parameters>);
}Default implementation — delegate to the real static
namespace <Namespace>;
/// <summary>
/// Default implementation that delegates to <static class>.
/// </summary>
public sealed class <WrapperName> : I<WrapperName>
{
public <return type> <MethodName>(<parameters>)
=> <StaticClass>.<MThis repository contains the .NET team's curated set of core skills and custom agents for coding agents. For information about the Agent Skills standard, see agentskills.io. 📊 Dashboard - Accuracy and efficiency scoring trends for contained plugins (
Repo: dotnet/skills
Other skills on dotnet-skills.
- /csharp-scripts
Run file-based C# apps with the .NET CLI when the user explicitly wants C#/.NET code without creating a project. Use for C# language/API experiments, one-file C# apps, small multi-file C# apps composed with `#:include`/`#:exclude`, or C# file-based apps linked with `#:ref`. Do
Open skill - /dotnet-pinvoke
Correctly call native (C/C++) libraries from .NET using P/Invoke and LibraryImport. Covers function signatures, string marshalling, memory lifetime, SafeHandle, and cross-platform patterns. USE FOR: writing new P/Invoke or LibraryImport declarations, reviewing or debugging
Open skill - /nuget-trusted-publishing
Set up NuGet trusted publishing (OIDC) on a GitHub Actions repo — replaces long-lived API keys with short-lived tokens. USE FOR: trusted publishing, NuGet OIDC, keyless NuGet publish, migrate from NuGet API key, NuGet/login, secure NuGet publishing. DO NOT USE FOR: publishing to
Open skill - /technology-selection
Guides technology selection and implementation of AI and ML features in .NET 8+ applications using ML.NET, Microsoft.Extensions.AI (MEAI), Microsoft Agent Framework (MAF), GitHub Copilot SDK, ONNX Runtime, and OllamaSharp. Covers the full spectrum from classic ML through modern
Open skill - /configuring-opentelemetry-dotnet
Configure OpenTelemetry distributed tracing, metrics, and logging in ASP.NET Core using the .NET OpenTelemetry SDK. Use when adding observability, setting up OTLP exporters, creating custom metrics/spans, or troubleshooting distributed trace correlation.
Open skill - /convert-blazor-server-to-webapp
Guides conversion of a pre-.NET 8 Blazor Server app into a .NET 8+ Blazor Web App. USE FOR: migrating apps that use AddServerSideBlazor and MapBlazorHub to the AddRazorComponents/MapRazorComponents model, converting _Host.cshtml to an App.razor root component, replacing
Open skill

