/generate-testability-wrappers
DO NOT USE when the target already consumes an injected interface or built-in abstraction such as IFileSystem or TimeProvider, even if the request says "generate a wrapper"; no new wrapper is needed. Use only when C# source calls an ambient/static dependency and no injectable
Claude Code
$ npx -y skills add dotnet/skills --skill generate-testability-wrappers --agent claude-code
Codex
$ npx skills add dotnet/skills --skill generate-testability-wrappers -a codex -y
Cursor
$ npx skills add dotnet/skills --skill generate-testability-wrappers -a cursor -y
Gemini CLI
$ npx skills add dotnet/skills --skill generate-testability-wrappers -a gemini-cli -y
GitHub Copilot
$ npx skills add dotnet/skills --skill generate-testability-wrappers -a github-copilot -y
OpenCode
$ npx skills add dotnet/skills --skill generate-testability-wrappers -a opencode -y
Windsurf
$ npx skills add dotnet/skills --skill generate-testability-wrappers -a windsurf -y
SKILL.md
generate-testability-wrappers.SKILL.mdname: generate-testability-wrappers
description: >
DO NOT USE when the target already consumes an injected interface or built-in
abstraction such as IFileSystem or TimeProvider, even if the request says
"generate a wrapper"; no new wrapper is needed. Use only when C# source calls
an ambient/static dependency and no injectable seam exists: first-time
TimeProvider, IHttpClientFactory, or System.IO.Abstractions adoption; minimal
Environment/Console/Process wrappers; IProcessRunner; DI registration; or an
ambient seam that preserves a static API. Exclude static detection
(detect-static-dependencies), migration to an existing/registered abstraction
(migrate-static-to-wrapper), one blocked behavior plus deterministic tests
(testability-obstacle), and 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 a released static API needs an ambient seam because signatures cannot change
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
If the target already consumes an injected interface or built-in abstraction, stop: do not add a second wrapper, project, registration, or test around that seam.
> A missing DI package does not by itself force an ambient seam. For an > instantiable class, prefer constructor injection and compose it explicitly or > show the requested registration. Use Step 5 when the API is static and its > signatures must stay static, or when the user explicitly forbids caller > construction/DI changes.
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) | | Composition | No | Existing DI framework, explicit/manual construction, or immutable static API | | 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:
- instantiable class: constructor injection, even if current callers compose the
object manually;
- existing container: add compile-ready registration following its conventions;
- public static API/signatures that cannot change: Step 5's ambient seam.
Check for a host builder, `IServiceCollection`, existing registrations, and construction sites. Do not infer "must remain static" merely because the project currently has no container.
Step 2: Generate built-in abstraction adoption (Time, HTTP)
TimeProvider (.NET 8+)
No wrapper code needed. Complete all four parts: production registration, constructor injection, a `FakeTimeProvider` test, and the testing package.
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));
The assertion must prove a time-dependent result after the fake is pinned or advanced. Merely constructing `FakeTimeProvider` is not a test. When the project has no container but the target is an instantiable class, inject `TimeProvider` anyway and show explicit production construction with `TimeProvider.System`; do not replace it with a custom static clock.
Before calling the adoption complete, verify the repository contains or the answer supplies every required artifact: the testing package reference, the production composition/registration, every affected constructor call, and a runnable fake-time test. A code fragment that omits one of those integration points is guidance, not a completed adoption.
TimeProvider (pre-.NET 8)
Guide: install `Microsoft.Bcl.TimeProvider` NuGet. Same API as above.
IHttpClientFactory
No wrapper code needed. Register a typed client via `builder.Services.AddHttpClient<MyService>()` and inject `HttpClient` directly into the class constructor. Preserve cancellation by passing the caller's token to the HTTP operation.
For tests, provide a complete fake `HttpMessageHandler` whose `SendAsync` returns a deterministic `HttpResponseMessage`, construct `Ht
Read more
name: generate-testability-wrappers description: > DO NOT USE when the target already consumes an injected interface or built-in abstraction such as IFileSystem or TimeProvider, even if the request says "generate a wrapper"; no new wrapper is needed. Use only when C# source calls an ambient/static dependency and no injectable seam exists: first-time TimeProvider, IHttpClientFactory, or System.IO.Abstractions adoption; minimal Environment/Console/Process wrappers; IProcessRunner; DI registration; or an ambient seam that preserves a static API. Exclude static detection (detect-static-dependencies), migration to an existing/registered abstraction (migrate-static-to-wrapper), one blocked behavior plus deterministic tests (testability-obstacle), and 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 a released static API needs an ambient seam because signatures cannot change
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
If the target already consumes an injected interface or built-in abstraction, stop: do not add a second wrapper, project, registration, or test around that seam.
> A missing DI package does not by itself force an ambient seam. For an > instantiable class, prefer constructor injection and compose it explicitly or > show the requested registration. Use Step 5 when the API is static and its > signatures must stay static, or when the user explicitly forbids caller > construction/DI changes.
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) | | Composition | No | Existing DI framework, explicit/manual construction, or immutable static API | | 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:
- instantiable class: constructor injection, even if current callers compose the
object manually;
- existing container: add compile-ready registration following its conventions;
- public static API/signatures that cannot change: Step 5's ambient seam.
Check for a host builder, `IServiceCollection`, existing registrations, and construction sites. Do not infer "must remain static" merely because the project currently has no container.
Step 2: Generate built-in abstraction adoption (Time, HTTP)
TimeProvider (.NET 8+)
No wrapper code needed. Complete all four parts: production registration, constructor injection, a `FakeTimeProvider` test, and the testing package.
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));
The assertion must prove a time-dependent result after the fake is pinned or advanced. Merely constructing `FakeTimeProvider` is not a test. When the project has no container but the target is an instantiable class, inject `TimeProvider` anyway and show explicit production construction with `TimeProvider.System`; do not replace it with a custom static clock.
Before calling the adoption complete, verify the repository contains or the answer supplies every required artifact: the testing package reference, the production composition/registration, every affected constructor call, and a runnable fake-time test. A code fragment that omits one of those integration points is guidance, not a completed adoption.
TimeProvider (pre-.NET 8)
Guide: install `Microsoft.Bcl.TimeProvider` NuGet. Same API as above.
IHttpClientFactory
No wrapper code needed. Register a typed client via `builder.Services.AddHttpClient<MyService>()` and inject `HttpClient` directly into the class constructor. Preserve cancellation by passing the caller's token to the HTTP operation.
For tests, provide a complete fake `HttpMessageHandler` whose `SendAsync` returns a deterministic `HttpResponseMessage`, construct `Ht
This repository contains the .NET team's curated set of portable skills and host-specific custom agents for coding agents. For information about the Agent Skills standard, see agentskills.io.
Other skills on skills.
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
Questions about generate-testability-wrappers.
- Which coding agents can use generate-testability-wrappers?
- generate-testability-wrappers installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

