Skip to content
Development
Skill

/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

From plugin
skills
5.8k106 skills20 agents

Claude Code

$ npx -y skills add dotnet/skills --skill generate-testability-wrappers --agent claude-code

SKILL.md

generate-testability-wrappers.SKILL.md
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

Read more
@dotnet
Ships withskills

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.

5.8k106 skills20 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
5.8k
Star history of dotnet/skills on GitHub, a chart by star-history.com

Other skills on skills.

csharp-scripts

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#…

@dotnet@dotnetInstall Skill
dotnet-pinvoke

dotnet-pinvoke

Correctly call native (C/C++) libraries from .NET using P/Invoke and LibraryImport. Covers function signatures, string…

@dotnet@dotnetInstall Skill
nuget-trusted-publishing

nuget-trusted-publishi…

Set up NuGet trusted publishing (OIDC) on a GitHub Actions repo — replaces long-lived API keys with short-lived tokens. USE FOR:…

@dotnet@dotnetInstall Skill
vectorization

vectorization

Design, implement, optimize, and review SIMD code in .NET. USE FOR: vectorizing scalar loops with TensorPrimitives,…

@dotnet@dotnetInstall Skill
technology-selection

technology-selection

Guides technology selection and implementation of AI and ML features in .NET 8+ applications using ML.NET,…

@dotnet@dotnetInstall Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills12d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react3d252k
@facebook@facebookInstall Skill

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.