Run .NET Tests
Purpose
Provide standardized instructions for running .NET 10 tests correctly. Ensures agents use the scripts/test-with-timeout.sh wrapper instead of direct dotnet test calls, which fail due to .NET 10's dual test runner architecture.
When to Use This Skill
-
Before committing code changes that affect C# code
-
When verifying bug fixes or new features
-
When running targeted tests during development
-
When the full test suite must pass before marking work complete
Hard Rules
Must
-
ALWAYS use scripts/test-with-timeout.sh wrapper - never call dotnet test directly
-
Run tests from the repository root (the wrapper handles directory changes automatically)
-
Use --solution src/tfplan2md.slnx for full test suite runs
-
Use --project with relative paths from src/ directory (e.g., --project tests/Oocx.TfPlan2Md.TUnit/ )
-
Wait for test completion and check exit code (0 = pass, non-zero = fail)
-
For snapshot test changes, use the update-test-snapshots skill instead of manual edits
Must Not
-
NEVER run dotnet test directly from command line - it will fail with MSB1001: Unknown switch errors from repo root
-
Never manually edit snapshot files in src/tests/Oocx.TfPlan2Md.TUnit/TestData/Snapshots/
-
use the update-test-snapshots skill
-
Never ignore test failures or skip tests to make CI pass
-
Never modify test expectations to match broken output - fix the code, not the tests
.NET 10 Dual Test Runner Issue
.NET 10 introduced two distinct test runners with incompatible CLI flags:
Working Directory Runner Mode --solution
--project
--treenode-filter
Result
Repo root (/ ) VSTest ❌ ❌ ❌ MSBuild error MSB1001: Unknown switch
src/ (where global.json lives) Microsoft.Testing.Platform ✅ ✅ ✅ Works correctly
The scripts/test-with-timeout.sh wrapper automatically:
-
Changes to the src/ directory (where global.json with "runner": "Microsoft.Testing.Platform" exists)
-
Normalizes any src/ -prefixed paths in arguments
-
Enforces a timeout to prevent hung test runs (default 120 seconds)
Why direct dotnet test fails: Running from repo root activates VSTest mode (no global.json ), which doesn't support --solution , --project , or --treenode-filter flags. The wrapper ensures tests run from src/ directory to activate Microsoft.Testing.Platform mode.
Common Test Commands
Run Full Test Suite
scripts/test-with-timeout.sh -- dotnet test --solution src/tfplan2md.slnx
Run Tests with Build
scripts/test-with-timeout.sh -- dotnet test --solution src/tfplan2md.slnx --configuration Release --verbosity normal
Run Tests Without Build (faster when already built)
scripts/test-with-timeout.sh -- dotnet test --solution src/tfplan2md.slnx --no-build
Override Timeout (for slow tests)
scripts/test-with-timeout.sh --timeout-seconds 300 -- dotnet test --solution src/tfplan2md.slnx
Run Specific Project Tests
scripts/test-with-timeout.sh -- dotnet test --project tests/Oocx.TfPlan2Md.TUnit/
TUnit Test Filtering
Important: This project uses TUnit (not xUnit). TUnit uses --treenode-filter instead of --filter . All TUnit-specific flags must come after -- in the wrapper command.
Filter by Class Name (hierarchical pattern)
scripts/test-with-timeout.sh -- dotnet test --project tests/Oocx.TfPlan2Md.TUnit/ --treenode-filter ///MarkdownRendererTests/*
Filter by Test Method Name
scripts/test-with-timeout.sh -- dotnet test --project tests/Oocx.TfPlan2Md.TUnit/ --treenode-filter ///*/Render_ValidPlan_ContainsSummarySection
Filter Pattern Explanation
TUnit uses hierarchical path patterns:
-
///*/TestMethodName
-
Match specific test method
-
///ClassName/*
-
Match all tests in a class
-
//Namespace.ClassName/
-
Match by namespace and class
Workflow Integration
When to Run Tests
During Development (after each meaningful change):
Run targeted tests for the area you modified
scripts/test-with-timeout.sh -- dotnet test --project tests/Oocx.TfPlan2Md.TUnit/ --treenode-filter ///YourTestClass/*
Before Committing (C# code changes only):
Run full test suite to ensure no regressions
scripts/test-with-timeout.sh -- dotnet test --solution src/tfplan2md.slnx --no-build
Skip Tests When (documentation/agent instructions only):
-
Changes are limited to .github/agents/ , .github/skills/ , .github/copilot-instructions.md , or docs/
-
No C# code was modified
-
The test suite doesn't validate these file types
Handling Test Failures
-
Read the failure output - TUnit provides detailed error messages
-
Identify the failing test - Look for the test method name and class
-
Run the specific failing test - Use --treenode-filter to isolate it
-
Debug and fix - Fix the code (never modify test expectations unless the test itself is wrong)
-
Re-run tests - Verify the fix works
-
Run full suite - Ensure no regressions before committing
Snapshot Test Changes
If tests fail because snapshot files need updating (intentional output changes):
-
Use the update-test-snapshots skill - Never manually edit snapshot files
-
Verify the new output is correct - Review the diff carefully
-
Include SNAPSHOT_UPDATE_OK in commit message - Document the intentional change
-
Re-run tests - Confirm all tests pass with new snapshots
Exit Codes
Exit Code Meaning Action
0 All tests passed Proceed with commit
1-123 Test failures Fix failing tests before committing
124 Timeout Increase timeout with --timeout-seconds or investigate hung tests
125 Wrapper error Check command syntax
Golden Example
Complete workflow for implementing a feature:
1. Build the project
dotnet build
2. Run targeted tests during development
scripts/test-with-timeout.sh -- dotnet test --project tests/Oocx.TfPlan2Md.TUnit/ --treenode-filter ///MyFeatureTests/*
3. Make changes, run tests again
scripts/test-with-timeout.sh -- dotnet test --project tests/Oocx.TfPlan2Md.TUnit/ --treenode-filter ///MyFeatureTests/*
4. Before committing, run full suite
scripts/test-with-timeout.sh -- dotnet test --solution src/tfplan2md.slnx --no-build
5. If all pass, commit changes
git add . git commit -m "feat: implement my feature"
Troubleshooting
Error: MSBuild error MSB1001: Unknown switch
Cause: Running dotnet test directly from repo root (VSTest mode doesn't support --solution /--project flags) Solution: Use scripts/test-with-timeout.sh wrapper
Error: Timeout after 120 seconds
Cause: Tests taking longer than default timeout Solution: Increase timeout with --timeout-seconds 300 (or higher)
Error: Test not found with --treenode-filter
Cause: Incorrect filter pattern or test doesn't exist Solution:
-
List all tests first: scripts/test-with-timeout.sh -- dotnet test --list-tests
-
Verify filter pattern matches TUnit hierarchical structure
Snapshot test failures after output changes
Cause: Intentional output format changes Solution: Use update-test-snapshots skill to regenerate snapshots correctly