Source profileQuality 91/100

dotnet/skills/plugins/dotnet-test/skills/testability-obstacle/SKILL.md

testability-obstacle

Make C# ambient-dependent behavior testable and add deterministic tests. USE FOR: DateTime/Task.Delay/File/Environment/Guid/Random, constructor injection for instance classes, preserving static APIs, nested override restore, parallel isolation, or no real I/O. DO NOT USE FOR: audits, wrapper-only/bulk migration, or an existing injectable seam.

Source repository stars
5,248
Declared platforms
0
Static risk flags
0
Last source update
2026-08-26
Source checked
2026-08-26

Decision brief

What it does: where it fits

Introduce the smallest behavior-preserving seam needed to test a specific C behavior, then add deterministic tests that prove both the behavior and the seam. The production edit is a means to the requested test, not an invitation to redesign adjacent code.

Best for

  • A requested test would otherwise read/write the real filesystem.
  • Behavior depends on the current time, delay, random value, environment, console,
  • The user explicitly permits or requests a safe production seam.

Not for

  • The dependency is already injected or passed as an argument. Write tests with
  • The user wants a repository-wide testability audit. Use

Compatibility matrix

Platform support, with evidence labels

PlatformStatusEvidenceWhat to check
CodexNot declaredNo explicit evidencePortability before use
Claude CodeNot declaredNo explicit evidencePortability before use
CursorNot declaredNo explicit evidencePortability before use
Gemini CLINot declaredNo explicit evidencePortability before use
Open the compatibility checker

Installation

Inspect first. Install second.

The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

Source-detected install commandSource
npx skills add https://github.com/dotnet/skills --skill "plugins/dotnet-test/skills/testability-obstacle"
Safe inspection promptEditorial

Inspect the Agent Skill "testability-obstacle" from https://github.com/dotnet/skills/blob/1b896e91feb0f613cb54a914f1efd2897810ae02/plugins/dotnet-test/skills/testability-obstacle/SKILL.md at commit 1b896e91feb0f613cb54a914f1efd2897810ae02. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

Workflow

What the source asks the agent to do

  1. 01

    Workflow

    Read the target production path and its existing tests. Identify the exact ambient operation preventing a deterministic test and the behavior that must remain unchanged. Do not run a repository-wide static scan for a single-class request.

    flow across await (AsyncLocal, not [ThreadStatic]);return IDisposable and restore the previous value, including nested scopes;default to the real production dependency;
  2. 02

    Step 1: Prove the obstacle

    Read the target production path and its existing tests. Identify the exact ambient operation preventing a deterministic test and the behavior that must remain unchanged. Do not run a repository-wide static scan for a single-class request.

    Read the target production path and its existing tests. Identify the exact ambient operation preventing a deterministic test and the behavior that must remain unchanged. Do not run a repository-wide static scan for a si…If an adequate seam already exists, stop refactoring and use it. This skill adds no value when a fake can already be supplied.
  3. 03

    Step 2: Select the smallest safe seam

    Choose by dependency and repository constraints:

    flow across await (AsyncLocal, not [ThreadStatic]);return IDisposable and restore the previous value, including nested scopes;default to the real production dependency;
  4. 04

    Step 3: Preserve behavior and API shape

    Keep the production change mechanical:

    Wrap only members used by the target behavior.Default implementations delegate directly to the original API.Preserve exceptions, path handling, time zone, and DateTime.Kind.
  5. 05

    Step 4: Keep production defaults wired

    Update every composition root or constructor call affected by the seam. Production must still use real time/filesystem/etc. by default. If the project uses DI, register the default implementation with the lifetime matching repository conventions. If it does not use DI, compose e…

    Update every composition root or constructor call affected by the seam. Production must still use real time/filesystem/etc. by default. If the project uses DI, register the default implementation with the lifetime match…Build the affected production project before writing tests. A compile failure here is a seam problem, not a test problem.

Permission review

Static risk signals and limitations

No configured static risk pattern was detected

This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.

Evidence record

Why each signal appears

EvidenceSourceComputedTestedEditorial
SignalValueEvidence typeMeaning
Quality score91/100ComputedDocumentation, specificity, maintenance, and trust rules
Repository stars5,248SourceRepository attention, not individual Skill quality
Compatibility0 platformsSourceDeclared in the catalog source record
Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

Pinned source

Provenance and original SKILL.md

Repository
dotnet/skills
Skill path
plugins/dotnet-test/skills/testability-obstacle/SKILL.md
Commit
1b896e91feb0f613cb54a914f1efd2897810ae02
License
MIT
Collected
2026-08-26
Default branch
main
View the original SKILL.md

Resolve a Testability Obstacle

Introduce the smallest behavior-preserving seam needed to test a specific C# behavior, then add deterministic tests that prove both the behavior and the seam. The production edit is a means to the requested test, not an invitation to redesign adjacent code.

When to Use

  • A requested test would otherwise read/write the real filesystem.
  • Behavior depends on the current time, delay, random value, environment, console, process, or another ambient dependency.
  • The user explicitly permits or requests a safe production seam.
  • Existing tests cannot control a dependency without process-global mutation.

When Not to Use

  • The dependency is already injected or passed as an argument. Write tests with a fake through the existing seam using code-testing-agent.
  • The user wants a repository-wide testability audit. Use detect-static-dependencies.
  • The user wants wrappers generated but not call sites/tests changed. Use generate-testability-wrappers.
  • The user requests a broad mechanical migration. Use migrate-static-to-wrapper, then generate tests separately.
  • The code is not C#/.NET.

Inputs

InputRequiredDescription
Behavior to testYesThe method/workflow and expected observable behavior
Target scopeNoDiscover the narrowest relevant file/project when omitted
Allowed production changesNoDefault to the minimum internal/constructor seam

Workflow

Step 1: Prove the obstacle

Read the target production path and its existing tests. Identify the exact ambient operation preventing a deterministic test and the behavior that must remain unchanged. Do not run a repository-wide static scan for a single-class request.

If an adequate seam already exists, stop refactoring and use it. This skill adds no value when a fake can already be supplied.

Step 2: Select the smallest safe seam

Choose by dependency and repository constraints:

DependencyPreferred seam
Current time / timersInject TimeProvider; use FakeTimeProvider in tests
FilesystemExisting repository file abstraction; otherwise the smallest interface or System.IO.Abstractions when already used/accepted
HTTPExisting typed HttpClient/handler or IHttpClientFactory seam
RandomnessInject Random or a minimal generator interface
Environment/console/processMinimal interface containing only members used by the target

The scoped AsyncLocal<T> rule applies to every static API that must retain its public static shape — clocks, filesystem access, environment lookups, identity generation, and randomness. The scope captures and restores the previous value; never implement Dispose() as an unconditional assignment to null.

Constructor injection is the default for instance classes. Reuse the repository's DI and naming conventions, but do not add a DI container to a class library just to satisfy this workflow.

For a static class or a public API that cannot change, use a scoped ambient seam only when constructor/parameter injection is impossible. The override must:

  • flow across await (AsyncLocal<T>, not [ThreadStatic]);
  • return IDisposable and restore the previous value, including nested scopes;
  • default to the real production dependency;
  • avoid a process-global mutable fake that makes tests non-parallel.

Use built-in fake-time-aware overloads instead of inventing an IDelay wrapper:

Ambient operationReplacement
Task.Delay(delay, token)Task.Delay(delay, timeProvider, token)
new CancellationTokenSource(delay)new CancellationTokenSource(delay, timeProvider)
PeriodicTimer(period)new PeriodicTimer(period, timeProvider) when the target framework provides it

Test delayed behavior by starting the operation, proving it is incomplete, advancing FakeTimeProvider, then awaiting it. Never wait for wall-clock time.

For a nested ambient override, disposing the inner scope must restore the outer value, not clear the slot. Capture the previous value per scope:

public static IDisposable OverrideClock(Func<DateTimeOffset> clock)
{
    var previous = s_clock.Value;
    s_clock.Value = clock;
    return new Scope(() => s_clock.Value = previous);
}

Add tests for both nesting and parallel async flows; parallel-only tests do not catch the common "dispose sets null" bug.

Step 3: Preserve behavior and API shape

Keep the production change mechanical:

  • Wrap only members used by the target behavior.
  • Default implementations delegate directly to the original API.
  • Preserve exceptions, path handling, time zone, and DateTime.Kind.
  • Keep existing public signatures unless the user explicitly permits an API change.
  • Do not move business logic into the wrapper or fix unrelated production bugs.

For time replacements:

  • DateTime.UtcNow -> timeProvider.GetUtcNow().UtcDateTime
  • DateTime.Now -> timeProvider.GetLocalNow().LocalDateTime
  • DateTimeOffset.UtcNow -> timeProvider.GetUtcNow()
  • DateTimeOffset.Now -> timeProvider.GetLocalNow()

Step 4: Keep production defaults wired

Update every composition root or constructor call affected by the seam. Production must still use real time/filesystem/etc. by default. If the project uses DI, register the default implementation with the lifetime matching repository conventions. If it does not use DI, compose explicitly; do not introduce a container.

Build the affected production project before writing tests. A compile failure here is a seam problem, not a test problem.

Step 5: Write deterministic tests

Use the repository's existing test project. If none exists, invoke scaffold-dotnet-test-project first.

Tests must supply controlled dependencies:

  • fixed/advanced time rather than wall-clock waiting;
  • an in-memory fake filesystem or hand-rolled fake rather than temp/real files;
  • no environment mutation, external process, console input, or network.

Assert the requested business result and at least one interaction/state observable that proves the fake dependency drove the path. Include a production-default test only when it can remain deterministic; never touch the real filesystem merely to prove the adapter delegates.

Step 6: Verify the complete path

Run the affected production build, targeted test project, and repository-level test command. Re-read the diff and confirm:

  1. every production change is required by the seam;
  2. no real ambient resource is used by the new tests;
  3. current-time semantics and public behavior are preserved;
  4. existing tests were not replaced or duplicated.

Output Contract

Provide a compact Requirement | Evidence table. Cite the production seam, production default wiring, exact test names, and passing commands. If a package restore or build blocks validation, report that blocker rather than claiming the tests pass.

Validation

  • The original obstacle was concrete and in the requested path.
  • An existing seam was reused when available.
  • The new abstraction exposes only members required by the target behavior.
  • Production defaults still delegate to the original dependency.
  • Time conversions preserve local/UTC and DateTime.Kind semantics.
  • Static ambient overrides are async-safe, scoped, nested, and reversible.
  • New tests use fixed/in-memory dependencies and no real I/O or wall clock.
  • Production build and targeted/repository tests pass.

Common Pitfalls

PitfallCorrective action
Refactoring before proving a blockerReuse an existing seam and write the test directly
Wrapping an entire static APIExpose only members exercised by the target
Converting UtcNow with .DateTimeUse .UtcDateTime to preserve DateTimeKind.Utc
Mutable static fake shared by testsUse constructor injection or a scoped AsyncLocal<T> override
Adding DI to a library with no containerCompose the dependency explicitly
Using temp files as a shortcutSupply an in-memory fake; the scenario requires no real I/O
Stopping after the refactor buildsWrite and run the behavior tests that justified the seam

Frequently asked questions

What to verify before installation and use

What does the testability-obstacle source document cover?

Introduce the smallest behavior-preserving seam needed to test a specific C behavior, then add deterministic tests that prove both the behavior and the seam. The production edit is a means to the requested test, not an invitation to redesign adjacent code.

How do I install testability-obstacle?

The source record exposes this install command: npx skills add https://github.com/dotnet/skills --skill "plugins/dotnet-test/skills/testability-obstacle". Inspect the command and pinned source before running it.

Alternatives

Compare before choosing

Computed 10029,095

garrytan/gbrain

bulk-ingestion

End-to-end discipline for turning any large data source (audio libraries, email takeouts, document corpora, chat exports, API dumps) into brain pages at scale. The lifecycle spine: SCHEMA → ACCESS → TRIAL → EVALUATE → IMPROVE → CODIFY → TEST → SKILLIFY → BULK → MONITOR. State is tracked in a durable JSON manifest (see MANIFEST-PATTERN.md) so any crash, session boundary, or subagent fan-out resumes from ground truth instead of memory.

Computed 10024,975

alirezarezvani/claude-skills

app-store-optimization

App Store Optimization (ASO) toolkit for researching keywords, analyzing competitor rankings, generating metadata suggestions, and improving app visibility on Apple App Store and Google Play Store. Use when the user asks about ASO, app store rankings, app metadata, app titles and descriptions, app store listings, app visibility, or mobile app marketing on iOS or Android. Supports keyword research and scoring, competitor keyword analysis, metadata optimization, A/B test planning, launch checklist

Computed 1005,248

dotnet/skills

migrate-vstest-to-mtp

Migrates .NET test projects from VSTest to Microsoft.Testing.Platform (MTP). Use when user asks to "migrate to MTP", "switch from VSTest", "enable Microsoft.Testing.Platform", "use MTP runner", set OutputType=Exe only for test projects in Directory.Build.props, or mentions EnableMSTestRunner, EnableNUnitRunner, or UseMicrosoftTestingPlatformRunner. USE FOR: MTP behavioral differences vs VSTest (exit code 8, zero tests discovered, --ignore-exit-code, TESTINGPLATFORM_EXITCODE_IGNORE); centralizing

Computed 100146

oaustegard/claude-skills

featuring

Generate hierarchical _FEATURES.md files that describe what a codebase DOES from a user/consumer perspective, anchored to source symbols via tree-sitting. Supports large complex codebases through feature-driven decomposition into sub-feature files. Uses a multi-pass synthesis: orientation → detail → overview rewrite. Use when someone says "what does this do", "document features", "feature inventory", "_FEATURES.md", or needs to understand a codebase's purpose before modifying it. Complements tre