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
managedcode-dotnet-skills
481182 skills17 agents
Install
$ npx -y skills add managedcode/dotnet-skills --skill generate-testability-wrappers --agent claude-code

How 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.

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

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
Ships withmanagedcode-dotnet-skills

Stop explaining .NET to your AI. Start building. We've all been there: asking Claude to use Entity Framework, only to get EF6 patterns in a .NET 8 project. Explaining to Copilot that Blazor Server and Blazor WebAssembly aren't the same thing.

Get the whole plugin

Other skills on managedcode-dotnet-skills.