From smock-plugin
Discovers .NET test projects, updates SMock package, finds untested code and non-SMock mocks, generates unit tests using SMock for statics like DateTime.Now and File APIs.
npx claudepluginhub svetlova/static-mock --plugin smock-pluginThis skill is limited to using the following tools:
Discover the test structure, find gaps and non-SMock mocking patterns, and write or update .NET tests using the SMock library — proactively and without asking the user for guidance on which classes to test.
Generates unit, integration, component, and e2e test suites with mocking strategies, edge case coverage, descriptive naming, and CI integration patterns. Activates on 'write tests', 'unit tests', 'mocking' requests.
Creates and manages mocks, stubs, spies, and test doubles to isolate unit tests from external dependencies like databases, APIs, and services. Supports Jest, Python unittest.mock, and Mockito.
Provides framework-agnostic testing principles on philosophy, structure, mocking boundaries, and patterns like AAA and Testing Trophy. Use for writing, reviewing, or debugging tests.
Share bugs, ideas, or general feedback.
Discover the test structure, find gaps and non-SMock mocking patterns, and write or update .NET tests using the SMock library — proactively and without asking the user for guidance on which classes to test.
An optional argument selects the default API style for generated tests:
sequential — use using var mock = Mock.Setup(...).Returns(value) (default)hierarchical — use Mock.Setup(..., () => { /* validation */ }).Returns(value)Never ask the user which classes or methods to test. Discover all of it automatically:
Moq, etc.)Proceed with all discovered work unless the user has explicitly scoped the request.
Before writing any test, evaluate whether the method under test actually requires mocking. Adding unnecessary mocks makes tests brittle and harder to read.
| Category | Examples |
|---|---|
| Non-deterministic BCL statics | DateTime.Now, DateTime.UtcNow, Guid.NewGuid(), Random.Next() |
| File system | File.Exists(), File.ReadAllText(), Directory.GetFiles(), Path.* |
| Environment / process | Environment.GetEnvironmentVariable(), Environment.MachineName, Environment.Exit() |
| Network / I/O | HttpClient static methods, WebRequest, Dns.GetHostName() |
| External services | Any static façade over a database, cache, message bus, or third-party API |
| System clock / timer | Stopwatch.GetTimestamp(), TimeProvider static members |
| Volatile or side-effecting statics | Logging singletons called via static, global config readers |
For each public method found, check: does the method body (or any method it transitively calls) reach a static that is non-deterministic, has I/O side effects, or depends on external state? If yes → SMock candidate. If no → write a plain test without mocking.
Before touching tests, map the source:
# Find all source .csproj files (exclude test projects)
find . -name "*.csproj" | grep -iv "\.tests" | head -20
# List public classes in source projects
grep -rl "public class\|public static class\|public interface" src/ \
--include="*.cs" | grep -iv "test" | head -40
# Find methods that call static classes (likely need mocking)
grep -rn "static\|DateTime\.\|File\.\|Path\.\|Environment\." src/ \
--include="*.cs" | grep -iv "test" | head -30
Read each discovered source file to understand:
After locating test .csproj files, verify all SMock references use the latest published version.
# Fetch the latest stable SMock version from NuGet
LATEST=$(curl -s "https://api.nuget.org/v3-flatcontainer/smock/index.json" \
| grep -oE '"[0-9]+\.[0-9]+\.[0-9]+"' | tr -d '"' | sort -V | tail -1)
echo "Latest SMock on NuGet: $LATEST"
# Find all .csproj files referencing SMock
grep -rl 'Include="SMock"' . --include="*.csproj"
For each .csproj found, read the current Version attribute on the SMock PackageReference:
grep 'SMock' path/to/Project.Tests.csproj
If the version is outdated (pinned to an older release, not *, or * is resolving to an old locked version):
.csproj — update the Version attribute to the fetched $LATEST valuedotnet restore in the src/ directory to pull the updated packageIf Version="*" is already present: run dotnet restore once to ensure the wildcard resolves to the latest, then confirm via:
dotnet list path/to/Project.Tests.csproj package | grep SMock
Skip this step only when no test .csproj files exist yet (they will be created in Step 2 using the latest version).
# Find existing test projects
find . -name "*.csproj" | grep -i "\.tests" | head -20
ls src/ 2>/dev/null | grep -i test
If a test project exists:
.csproj for TargetFramework and package references# Find tests NOT using SMock
grep -rl "using StaticMock" src/ --include="*.cs" | sort > /tmp/smock_files.txt
find src/ -name "*Tests*.cs" | sort > /tmp/all_test_files.txt
comm -23 /tmp/all_test_files.txt /tmp/smock_files.txt # tests missing SMock
# Find competing mock patterns to replace
grep -rn "new Mock<\|\.Setup(\|Moq\.\|FakeItEasy\.\|wrapper\|wrapper class" \
src/ --include="*.cs" | grep -i test
If non-SMock patterns are found: proceed to Step 2 / Step 3 to fix them.
If every test file already uses SMock (the comm output is empty and no competing patterns found): proceed directly to Step 1.5 for a full correctness and coverage audit.
If no test project exists: proceed to Step 2.
Run this step whenever the codebase is already on SMock and the skill is invoked without a specific fix request. The goal is to surface real problems — incorrect usage and uncovered scenarios — not cosmetic issues.
# Sequential mocks missing 'using var' (leaves hook active, corrupts other tests)
grep -rn "var [a-zA-Z_]* = Mock\.Setup" src/ --include="*.cs" | grep -v "using var"
# Standalone 'It' not accessed through context lambda
grep -rn "\bIt\.IsAny\|\bIt\.Is(" src/ --include="*.cs"
# Callback called on IFuncMock (only valid on IActionMock)
grep -rn "\.Returns(.*)\s*\n?\s*\.Callback\|\.Returns(.*).Callback" src/ --include="*.cs"
# Missing <Optimize>false</Optimize> in test .csproj files
grep -rL "Optimize>false" . --include="*.csproj" | grep -i test
For each hit, read the surrounding context and determine if it is a genuine violation. Report findings grouped by file, with line numbers.
For each source method identified as an SMock candidate in Step 0 (non-deterministic / I/O / external):
Mock.Setup call targeting the external dependency# List all public methods in source that touch non-deterministic statics
grep -rn "DateTime\.\|File\.\|Environment\.\|Guid\.NewGuid\|Random\." src/ \
--include="*.cs" | grep -iv test
# For each source class, check if a *Tests class exists
# e.g. for FileService.cs → grep for FileServiceTests in test files
For each gap found (mock candidate with no test or no Mock.Setup):
After completing the audit, produce a concise report before making any changes:
## SMock Audit Report
### Correctness Issues
- [FILE:LINE] Missing `using var` on Sequential mock
- [FILE:LINE] Standalone `It.IsAny<T>()` — should be `context.It.IsAny<T>()`
- [FILE] Missing <Optimize>false</Optimize> in .csproj
### Coverage Gaps
- [SourceClass.Method] calls DateTime.Now — no test mocking this dependency
- [SourceClass.OtherMethod] calls File.ReadAllText — test exists but does not mock File access
### All Clear
- (listed items that were checked and passed)
Then fix all issues found using Step 3. If nothing is found, report "All clear — SMock usage is correct and coverage is complete."
Naming conventions (non-negotiable):
| Item | Convention | Example |
|---|---|---|
| Test project | OriginalProject.Tests | MyService.Tests |
| Test class | OriginalClassNameTests | FileServiceTests |
| Test method | Test + MethodName + Description | TestGetUserReturnsActiveUser |
src/OriginalProject.Tests/ directory.csproj file (see references/test-structure.md for full template — includes <Optimize>false</Optimize>, SMock package ref, NUnit)dotnet sln add src/OriginalProject.Tests/OriginalProject.Tests.csprojAlways add using StaticMock; and using NUnit.Framework.Legacy; at the top of each test file.
For every public class or method discovered in Step 0 that lacks adequate SMock coverage: write tests. Don't skip a class because it wasn't mentioned by the user.
For existing tests using non-SMock patterns: replace them with the SMock equivalents below.
[Test]
public void TestMethodNameDoesExpectedBehavior()
{
using var mock = Mock.Setup(() => TargetClass.StaticMethod())
.Returns(expectedValue);
var result = systemUnderTest.MethodThatCallsStatic();
ClassicAssert.AreEqual(expectedValue, result);
}
With parameter matching:
using var mock = Mock.Setup(context => TargetClass.Method(context.It.IsAny<string>()))
.Returns(expectedValue);
[Test]
public void TestMethodNameWithInlineValidation()
{
Mock.Setup(context => TargetClass.Method(context.It.IsAny<string>()), () =>
{
var result = TargetClass.Method("input");
ClassicAssert.IsNotNull(result);
ClassicAssert.AreEqual(expectedValue, result);
}).Returns(expectedValue);
}
using var — omitting it leaves the hook active and corrupts other testsIt only through the lambda's context parameter: context.It.IsAny<T>() — never standalone It.ReturnsAsync(value) (preferred) or .Returns(Task.FromResult(value))IActionMock): .Callback<T>(p => { }) — only IActionMock has Callback, not IFuncMock.Throws<ExceptionType>() — requires ExceptionType to have a parameterless constructor.Throws<ExceptionType>(new object[] { "message", arg2 }) or .Throws(typeof(ExceptionType), "message")[MethodImpl(MethodImplOptions.NoOptimization)] to a test method if mocks don't intercept in Release buildsAfter writing tests, update (or create) the CLAUDE.md at the project root:
CLAUDE.md exists at the root of the project being testedSMock)## Testing
Tests live in `src/OriginalProject.Tests/`. Use the SMock library (`using StaticMock;`) for all mocking.
- **Default style**: Sequential (`using var mock = Mock.Setup(...).Returns(value)`)
- **Test class naming**: `OriginalClassNameTests`
- **Test method naming**: `Test` + MethodName [+ Description]
cd src
# Build in Debug (required — optimizer can break SMock hooks)
dotnet build --configuration Debug
# Run the tests for the affected project
dotnet test --configuration Debug --framework net8.0 --verbosity minimal \
--filter "FullyQualifiedName~OriginalProject.Tests"
If the build fails:
using statements (using StaticMock;, using NUnit.Framework;, etc.).csproj references both the SMock package and the source project<Optimize>false</Optimize> is present in the test .csprojIf tests fail:
using var is used for all Sequential mocks.Throws<T>() has the parameterless constructor constraint satisfiedcontext.It.IsAny<T>() is used — not a standalone It referenceConsult these references for detailed information:
references/api-reference.md — All Mock.Setup overloads, IFuncMock, IActionMock, IAsyncFuncMock, SetupProperty, SetupAction, SetupDefault, complete Throws and Callback signaturesreferences/test-structure.md — Full .csproj template, solution integration, namespace conventions, NUnit attributes, test organizationreferences/patterns.md — Common patterns: time-dependent testing, file system mocking, async mocking, callbacks, conditional returns, exception testing, multiple coordinated mocks