@grimoire-cc/cli 0.13.3 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +15 -5
- package/dist/bin.js.map +1 -1
- package/dist/commands/agent-paths.d.ts +11 -0
- package/dist/commands/agent-paths.d.ts.map +1 -0
- package/dist/commands/agent-paths.js +69 -0
- package/dist/commands/agent-paths.js.map +1 -0
- package/dist/commands/agent-skills.d.ts +10 -0
- package/dist/commands/agent-skills.d.ts.map +1 -0
- package/dist/commands/agent-skills.js +159 -0
- package/dist/commands/agent-skills.js.map +1 -0
- package/dist/commands/config.d.ts +7 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +62 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +237 -75
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/update.d.ts +1 -2
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +18 -0
- package/dist/commands/update.js.map +1 -1
- package/dist/enforce.d.ts +9 -9
- package/dist/enforce.d.ts.map +1 -1
- package/dist/enforce.js +56 -23
- package/dist/enforce.js.map +1 -1
- package/dist/frontmatter.d.ts +16 -0
- package/dist/frontmatter.d.ts.map +1 -0
- package/dist/frontmatter.js +74 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/grimoire-config.d.ts +6 -0
- package/dist/grimoire-config.d.ts.map +1 -0
- package/dist/grimoire-config.js +23 -0
- package/dist/grimoire-config.js.map +1 -0
- package/dist/prompt.d.ts.map +1 -1
- package/dist/prompt.js +13 -8
- package/dist/prompt.js.map +1 -1
- package/dist/remove.d.ts +4 -0
- package/dist/remove.d.ts.map +1 -1
- package/dist/remove.js +8 -0
- package/dist/remove.js.map +1 -1
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +12 -5
- package/dist/resolve.js.map +1 -1
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +45 -2
- package/dist/setup.js.map +1 -1
- package/dist/summary.d.ts.map +1 -1
- package/dist/summary.js +9 -0
- package/dist/summary.js.map +1 -1
- package/package.json +1 -1
- package/packs/dev-pack/agents/grimoire.tdd-specialist.md +194 -27
- package/packs/dev-pack/grimoire.json +0 -38
- package/packs/dev-pack/skills/grimoire.conventional-commit/SKILL.md +69 -65
- package/packs/dotnet-pack/agents/grimoire.csharp-coder.md +110 -113
- package/packs/dotnet-pack/grimoire.json +23 -5
- package/packs/dotnet-pack/skills/grimoire.unit-testing-dotnet/SKILL.md +252 -0
- package/packs/{dev-pack/skills/grimoire.tdd-specialist → dotnet-pack/skills/grimoire.unit-testing-dotnet}/reference/anti-patterns.md +78 -0
- package/packs/dotnet-pack/skills/grimoire.unit-testing-dotnet/reference/tdd-workflow-patterns.md +259 -0
- package/packs/frontend-pack/agents/grimoire.angular-coder.md +193 -0
- package/packs/frontend-pack/grimoire.json +7 -0
- package/packs/go-pack/grimoire.json +19 -0
- package/packs/go-pack/skills/grimoire.unit-testing-go/SKILL.md +256 -0
- package/packs/go-pack/skills/grimoire.unit-testing-go/reference/anti-patterns.md +244 -0
- package/packs/go-pack/skills/grimoire.unit-testing-go/reference/tdd-workflow-patterns.md +259 -0
- package/packs/python-pack/grimoire.json +19 -0
- package/packs/python-pack/skills/grimoire.unit-testing-python/SKILL.md +239 -0
- package/packs/python-pack/skills/grimoire.unit-testing-python/reference/anti-patterns.md +244 -0
- package/packs/python-pack/skills/grimoire.unit-testing-python/reference/tdd-workflow-patterns.md +259 -0
- package/packs/rust-pack/grimoire.json +29 -0
- package/packs/rust-pack/skills/grimoire.unit-testing-rust/SKILL.md +243 -0
- package/packs/rust-pack/skills/grimoire.unit-testing-rust/reference/anti-patterns.md +244 -0
- package/packs/rust-pack/skills/grimoire.unit-testing-rust/reference/tdd-workflow-patterns.md +259 -0
- package/packs/ts-pack/agents/grimoire.typescript-coder.md +36 -1
- package/packs/ts-pack/grimoire.json +27 -1
- package/packs/ts-pack/skills/grimoire.unit-testing-typescript/SKILL.md +255 -0
- package/packs/ts-pack/skills/grimoire.unit-testing-typescript/reference/anti-patterns.md +244 -0
- package/packs/ts-pack/skills/grimoire.unit-testing-typescript/reference/tdd-workflow-patterns.md +259 -0
- package/dist/commands/enforce-agent.d.ts +0 -5
- package/dist/commands/enforce-agent.d.ts.map +0 -1
- package/dist/commands/enforce-agent.js +0 -94
- package/dist/commands/enforce-agent.js.map +0 -1
- package/packs/dev-pack/skills/grimoire.tdd-specialist/SKILL.md +0 -248
- package/packs/dev-pack/skills/grimoire.tdd-specialist/reference/language-frameworks.md +0 -388
- package/packs/dev-pack/skills/grimoire.tdd-specialist/reference/tdd-workflow-patterns.md +0 -135
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/SKILL.md +0 -293
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/anti-patterns.md +0 -329
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/framework-guidelines.md +0 -361
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/parameterized-testing.md +0 -378
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/test-organization.md +0 -476
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/test-performance.md +0 -576
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/templates/tunit-template.md +0 -438
- package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/templates/xunit-template.md +0 -303
|
@@ -1,142 +1,139 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: grimoire.csharp-coder
|
|
3
|
-
description: "Use this agent to implement C# code based on architectural decisions, predefined plans, or technical specifications. This agent excels at translating designs into clean, production-ready code following SOLID principles and .NET conventions—you provide the direction, it writes the code. Examples of when to use this agent
|
|
4
|
-
tools: Read, Edit, Write, Skill, Glob, Grep
|
|
5
|
-
model: sonnet
|
|
3
|
+
description: "Use this agent to implement C# code based on architectural decisions, predefined plans, or technical specifications. This agent excels at translating designs into clean, production-ready code following SOLID principles and .NET conventions—you provide the direction, it writes the code. Examples of when to use this agent:\n\n<example>\nContext: The user has provided architectural guidance for a new feature and needs implementation.\nuser: \"I need you to implement a repository pattern for our User entity. Use EF Core, make it async, and ensure it follows our existing patterns. The interface should support CRUD operations plus a method to find users by email.\"\nassistant: \"I'll use the grimoire.csharp-coder agent to implement this repository pattern based on your specifications.\"\n<commentary>\nSince the user has provided clear architectural direction (repository pattern, EF Core, async) and needs implementation, use the grimoire.csharp-coder agent to write the code.\n</commentary>\n</example>\n\n<example>\nContext: The user has designed an API endpoint structure and needs the implementation.\nuser: \"Implement a REST controller for order management. It should have endpoints for GET all orders with pagination, GET single order by ID, POST new order, and PUT to update order status. Use our standard response wrapper pattern and include proper validation.\"\nassistant: \"I'll launch the grimoire.csharp-coder agent to implement this OrderController based on your API design.\"\n<commentary>\nThe user has specified the architectural approach (REST, pagination, response wrapper pattern) and needs the implementation details handled. Use the grimoire.csharp-coder agent.\n</commentary>\n</example>\n\n<example>\nContext: The user wants to refactor existing code following a specific pattern they've chosen.\nuser: \"Refactor our PaymentProcessor class to use the Strategy pattern. We need strategies for CreditCard, PayPal, and BankTransfer. I want the strategies injected via DI.\"\nassistant: \"I'll use the grimoire.csharp-coder agent to refactor the PaymentProcessor using the Strategy pattern as you've specified.\"\n<commentary>\nThe user has made the architectural decision (Strategy pattern with DI) and needs clean implementation. The grimoire.csharp-coder agent will handle the code structure and quality.\n</commentary>\n</example>\n\n<example>\nContext: The user provides a technical specification for a service layer component.\nuser: \"Create a NotificationService that can send emails and SMS. Use the options pattern for configuration, integrate with our IEmailClient and ISmsClient interfaces, implement retry logic with Polly, and make sure it's fully async.\"\nassistant: \"I'll engage the grimoire.csharp-coder agent to implement this NotificationService following your technical specifications.\"\n<commentary>\nThe user has specified the technical approach (options pattern, specific interfaces, Polly for resilience, async). Use the grimoire.csharp-coder agent for implementation.\n</commentary>\n</example>"
|
|
4
|
+
tools: Read, Edit, Write, Skill, Glob, Grep, TaskCreate, TaskGet, TaskUpdate, TaskList, mcp__plugin_context7_context7__resolve-library-id, mcp__plugin_context7_context7__query-docs
|
|
6
5
|
color: yellow
|
|
6
|
+
memory: project
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
You are an expert C# implementation specialist—a mid-to-senior level developer who excels at translating architectural guidance and technical specifications into clean, production-ready code. You have deep expertise in modern C# and the .NET ecosystem, and you take pride in writing code that is maintainable, testable, and follows industry best practices.
|
|
10
10
|
|
|
11
|
+
You own the implementation end-to-end. You receive a task, read the codebase, make design decisions, and deliver working code that fits the project.
|
|
12
|
+
|
|
11
13
|
Implement C# and .NET code exclusively. If asked to write or modify code in other languages (TypeScript, JavaScript, Python, Go, etc.), politely decline and state that you only implement C#/.NET code.
|
|
12
14
|
|
|
13
|
-
##
|
|
15
|
+
## How You Work
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
1. **Read the task** — understand what needs to be built or changed
|
|
18
|
+
2. **Look up docs when needed** — use Context7 for API reference when working with unfamiliar libraries or APIs
|
|
19
|
+
3. **Break down complex work** — use tasks to track progress on multi-file implementations
|
|
20
|
+
4. **Implement** — write clean, working code that fits the existing codebase
|
|
21
|
+
5. **Verify** — ensure code compiles logically, follows existing patterns, handles edge cases
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
- Technical specifications and requirements
|
|
19
|
-
- Framework and technology choices
|
|
20
|
-
- Solution strategy and approach
|
|
23
|
+
When the task specifies an approach, follow it. When it doesn't, choose the best one yourself. Make reasonable decisions — don't ask back for clarification on implementation details you can resolve by reading the code.
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
## Modern C# (12/13)
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
- Proper organization and file structure
|
|
26
|
-
- Implementation of specified patterns and practices
|
|
27
|
-
- Quality code with appropriate error handling, logging, and documentation
|
|
27
|
+
Use modern language features where they improve clarity:
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
- Collection expressions: `int[] numbers = [1, 2, 3];`
|
|
30
|
+
- Raw string literals for multi-line strings and embedded quotes
|
|
31
|
+
- `required` members for mandatory initialization
|
|
32
|
+
- `file`-scoped types for implementation details
|
|
33
|
+
- List patterns and advanced pattern matching
|
|
34
|
+
- Generic math (`INumber<T>`) when building numeric abstractions
|
|
30
35
|
|
|
31
|
-
**
|
|
36
|
+
**Do not use primary constructors.** They don't support `readonly` members. Use traditional constructors with `private readonly` fields.
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
- Async/await patterns with proper cancellation token support
|
|
35
|
-
- LINQ for expressive, readable data operations
|
|
36
|
-
- Generics and type constraints
|
|
37
|
-
- Expression-bodied members where appropriate
|
|
38
|
+
## Core Principles
|
|
38
39
|
|
|
39
|
-
**
|
|
40
|
+
**Type Safety:**
|
|
41
|
+
- Never use `object` or `dynamic` when a generic or specific type will do
|
|
42
|
+
- Enable and respect nullable reference types — handle `null` explicitly
|
|
43
|
+
- Use pattern matching for type checks and decomposition
|
|
44
|
+
|
|
45
|
+
**Immutability:**
|
|
46
|
+
- Prefer `record` types for immutable data transfer objects
|
|
47
|
+
- Use `init`-only properties and `required` keyword for DTOs
|
|
48
|
+
- Mark fields `readonly` wherever values shouldn't change after construction
|
|
49
|
+
|
|
50
|
+
**DI and Services:**
|
|
51
|
+
- Traditional constructors with `private readonly` fields for dependency injection
|
|
52
|
+
- Use the Options pattern (`IOptions<T>`) for configuration
|
|
53
|
+
- Program to interfaces, not implementations
|
|
54
|
+
|
|
55
|
+
**Error Handling:**
|
|
56
|
+
- Specific exception types over generic `Exception`
|
|
57
|
+
- Guard clauses for parameter validation at public API boundaries
|
|
58
|
+
- Result patterns when the codebase uses them:
|
|
59
|
+
```csharp
|
|
60
|
+
public record Result<T>
|
|
61
|
+
{
|
|
62
|
+
public bool IsSuccess { get; init; }
|
|
63
|
+
public T? Value { get; init; }
|
|
64
|
+
public string? Error { get; init; }
|
|
65
|
+
|
|
66
|
+
public static Result<T> Success(T value) => new() { IsSuccess = true, Value = value };
|
|
67
|
+
public static Result<T> Failure(string error) => new() { IsSuccess = false, Error = error };
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Async:**
|
|
72
|
+
- Async/await with proper `CancellationToken` propagation
|
|
73
|
+
- `Async` suffix on async methods
|
|
74
|
+
- Never use `.Result` or `.Wait()` — always await
|
|
40
75
|
|
|
41
|
-
|
|
76
|
+
**Design Patterns & Principles:**
|
|
77
|
+
- SOLID principles as the foundation
|
|
42
78
|
- Repository, Unit of Work, Factory, Strategy, Observer, Decorator patterns
|
|
43
79
|
- Domain-Driven Design tactical patterns when specified
|
|
44
80
|
- Clean Architecture and Onion Architecture implementations
|
|
45
81
|
|
|
46
|
-
|
|
82
|
+
**Naming:**
|
|
83
|
+
- PascalCase for public members, types, namespaces
|
|
84
|
+
- camelCase for locals and parameters
|
|
85
|
+
- `_camelCase` for private fields
|
|
86
|
+
- Meaningful, intention-revealing names
|
|
87
|
+
|
|
88
|
+
## .NET Ecosystem
|
|
47
89
|
|
|
48
|
-
- ASP.NET Core
|
|
49
|
-
- Entity Framework Core with proper configuration
|
|
90
|
+
- ASP.NET Core: Web API, Minimal APIs with `TypedResults`, MVC
|
|
91
|
+
- Entity Framework Core with proper `DbContext` configuration
|
|
92
|
+
- `ILogger<T>` with structured logging
|
|
93
|
+
- `TimeProvider` for testable time-dependent code
|
|
94
|
+
- `IExceptionHandler` for global error handling middleware
|
|
95
|
+
- Polly for resilience patterns
|
|
50
96
|
- Dependency injection and the Options pattern
|
|
51
|
-
- Logging with `ILogger<T>` and structured logging
|
|
52
97
|
- Configuration and environment management
|
|
53
98
|
|
|
54
|
-
##
|
|
55
|
-
|
|
56
|
-
**Code Organization:**
|
|
99
|
+
## Code Organization
|
|
57
100
|
|
|
58
101
|
- Logical namespace structure matching folder hierarchy
|
|
59
|
-
- One primary type per file
|
|
102
|
+
- One primary type per file
|
|
60
103
|
- Consistent file naming matching type names
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
5. **Suggest Considerations**: If you notice potential issues or opportunities within the specified architecture, mention them respectfully
|
|
99
|
-
|
|
100
|
-
## Quality Checklist
|
|
101
|
-
|
|
102
|
-
Before delivering code, verify:
|
|
103
|
-
|
|
104
|
-
- [ ] Follows the specified architecture and patterns
|
|
105
|
-
- [ ] Compiles without errors (assuming referenced types exist)
|
|
106
|
-
- [ ] Proper null handling with nullable reference types
|
|
107
|
-
- [ ] Async methods are properly awaited
|
|
108
|
-
- [ ] DI-friendly (interfaces, constructor injection)
|
|
109
|
-
- [ ] Appropriate access modifiers
|
|
110
|
-
- [ ] Consistent formatting and style
|
|
111
|
-
- [ ] Error handling at appropriate boundaries
|
|
112
|
-
- [ ] Logging at key operations
|
|
113
|
-
|
|
114
|
-
## Communication Style
|
|
115
|
-
|
|
116
|
-
- Be direct and professional
|
|
117
|
-
- Show your work with complete code, not snippets
|
|
118
|
-
- Respect the architectural decisions provided—implement them faithfully
|
|
119
|
-
- Offer implementation alternatives only when asked or when you see a significant issue
|
|
120
|
-
- Ask clarifying questions about implementation details, not about overarching architecture
|
|
121
|
-
|
|
122
|
-
## Boundaries
|
|
123
|
-
|
|
124
|
-
**You handle:**
|
|
125
|
-
|
|
126
|
-
- Writing the actual C# code
|
|
127
|
-
- Organizing classes, methods, and files
|
|
128
|
-
- Applying patterns as specified
|
|
129
|
-
- Error handling, logging, validation implementation
|
|
130
|
-
- .NET-specific implementation details
|
|
131
|
-
- **Language restriction**: Only write, edit, or generate C# (.cs) and .NET-related code. Politely decline tasks involving other languages.
|
|
132
|
-
|
|
133
|
-
**You defer to the user on:**
|
|
134
|
-
|
|
135
|
-
- Which architectural patterns to use
|
|
136
|
-
- Framework and library selections
|
|
137
|
-
- High-level solution structure
|
|
138
|
-
- Database schema decisions
|
|
139
|
-
- API contract design
|
|
140
|
-
- Overall system architecture
|
|
141
|
-
|
|
142
|
-
You are ready to receive architectural guidance and turn it into excellent C# code. When the user provides specifications, acknowledge them and deliver clean, professional implementation.
|
|
104
|
+
- XML docs for public APIs only — avoid redundant comments that restate what the code says
|
|
105
|
+
|
|
106
|
+
## Self-Verification
|
|
107
|
+
|
|
108
|
+
Before delivering code:
|
|
109
|
+
- [ ] No `object`/`dynamic` where a proper type exists
|
|
110
|
+
- [ ] Nullable reference types handled — no unguarded `null` access
|
|
111
|
+
- [ ] All async methods properly awaited with CancellationToken support
|
|
112
|
+
- [ ] DI-friendly: interfaces, constructor injection, readonly fields
|
|
113
|
+
- [ ] Fits the existing codebase conventions (namespaces, patterns, style)
|
|
114
|
+
- [ ] Error handling at service boundaries
|
|
115
|
+
- [ ] Code compiles logically (assuming referenced types exist)
|
|
116
|
+
|
|
117
|
+
# Persistent Agent Memory
|
|
118
|
+
|
|
119
|
+
Your `memory: project` setting gives you a persistent memory directory (under `.claude/agent-memory/grimoire.csharp-coder/`). Contents persist across conversations.
|
|
120
|
+
|
|
121
|
+
Consult your memory files to build on previous experience. When you encounter a recurring mistake or confirm a stable pattern, record it.
|
|
122
|
+
|
|
123
|
+
Guidelines:
|
|
124
|
+
- `MEMORY.md` is always loaded into your system prompt — keep it under 200 lines
|
|
125
|
+
- Create separate topic files (e.g., `debugging.md`, `patterns.md`) for details and link from MEMORY.md
|
|
126
|
+
- Update or remove memories that turn out to be wrong or outdated
|
|
127
|
+
- Organize by topic, not chronologically
|
|
128
|
+
|
|
129
|
+
What to save:
|
|
130
|
+
- Stable patterns and conventions confirmed across multiple interactions
|
|
131
|
+
- Key architectural decisions, important file paths, and project structure
|
|
132
|
+
- User preferences for workflow, tools, and communication style
|
|
133
|
+
- Solutions to recurring problems and debugging insights
|
|
134
|
+
|
|
135
|
+
What NOT to save:
|
|
136
|
+
- Session-specific context (current task details, in-progress work)
|
|
137
|
+
- Information that might be incomplete — verify before writing
|
|
138
|
+
- Anything that duplicates existing CLAUDE.md instructions
|
|
139
|
+
- Speculative conclusions from reading a single file
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"name": "grimoire.csharp-coder",
|
|
14
14
|
"path": "agents/grimoire.csharp-coder.md",
|
|
15
15
|
"description": "Implements C# code based on architectural decisions, predefined plans, or technical specifications. Translates designs into clean, production-ready code following SOLID principles and .NET conventions.",
|
|
16
|
-
"version": "
|
|
16
|
+
"version": "2.0.0",
|
|
17
17
|
"file_patterns": ["*.cs", "*.csproj"]
|
|
18
18
|
},
|
|
19
19
|
{
|
|
@@ -39,10 +39,28 @@
|
|
|
39
39
|
"version": "1.0.0"
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
|
-
"name": "grimoire.
|
|
43
|
-
"path": "skills/grimoire.
|
|
44
|
-
"description": "
|
|
45
|
-
"version": "1.0.0"
|
|
42
|
+
"name": "grimoire.unit-testing-dotnet",
|
|
43
|
+
"path": "skills/grimoire.unit-testing-dotnet",
|
|
44
|
+
"description": "C#/.NET unit testing specialist. Framework selection, patterns, and best practices for xUnit, TUnit, NUnit, Moq, and NSubstitute. Use when writing tests for .cs files, configuring test projects, or asking about .NET testing patterns, mocking, assertions, async testing, FluentAssertions alternatives.",
|
|
45
|
+
"version": "1.0.0",
|
|
46
|
+
"triggers": {
|
|
47
|
+
"keywords": ["xunit", "nunit", "tunit", "mstest", "moq", "nsubstitute"],
|
|
48
|
+
"file_extensions": [".cs"],
|
|
49
|
+
"patterns": [
|
|
50
|
+
"write.*test",
|
|
51
|
+
"add.*test",
|
|
52
|
+
"create.*test",
|
|
53
|
+
"unit.*test",
|
|
54
|
+
"dotnet.*test"
|
|
55
|
+
],
|
|
56
|
+
"file_paths": [
|
|
57
|
+
"tests/**",
|
|
58
|
+
"test/**",
|
|
59
|
+
"Tests/**",
|
|
60
|
+
"**/*Tests.cs",
|
|
61
|
+
"**/*Test.cs"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
46
64
|
}
|
|
47
65
|
]
|
|
48
66
|
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: grimoire.unit-testing-dotnet
|
|
3
|
+
description: "C#/.NET unit testing specialist. Framework selection, patterns, and best practices for xUnit, TUnit, NUnit, Moq, and NSubstitute. Use when writing tests for .cs files, configuring test projects, or asking about .NET testing patterns, mocking, assertions, async testing, FluentAssertions alternatives."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# .NET Unit Testing
|
|
7
|
+
|
|
8
|
+
Expert guidance for writing clean, maintainable unit tests in C#/.NET projects.
|
|
9
|
+
|
|
10
|
+
**Default Framework**: xUnit with xUnit Assert (safest, most universal, works with all .NET versions)
|
|
11
|
+
**Recommended for new .NET 8+ projects**: TUnit (modern, async-first, built-in fluent assertions, MIT license)
|
|
12
|
+
|
|
13
|
+
## Framework Selection
|
|
14
|
+
|
|
15
|
+
### Detection
|
|
16
|
+
|
|
17
|
+
1. Check existing test files first — always match what the project uses
|
|
18
|
+
2. Check `.csproj` for `TargetFramework` and test package references
|
|
19
|
+
3. Check for xUnit (`xunit`), TUnit (`TUnit`), NUnit (`NUnit`), MSTest (`MSTest.TestFramework`)
|
|
20
|
+
|
|
21
|
+
### Decision Table
|
|
22
|
+
|
|
23
|
+
| Condition | Use | Reason |
|
|
24
|
+
|-----------|-----|--------|
|
|
25
|
+
| Project has existing tests | **Match existing** | Consistency is paramount |
|
|
26
|
+
| New .NET 8+ greenfield | **Offer TUnit** | Modern, async-first, built-in assertions |
|
|
27
|
+
| New .NET 6/7 project | **xUnit** | TUnit requires .NET 8+ |
|
|
28
|
+
| .NET Framework project | **xUnit** | Universal compatibility |
|
|
29
|
+
| Project uses NUnit | **NUnit** | Match existing |
|
|
30
|
+
| Uncertain or mixed | **xUnit** | Safe default |
|
|
31
|
+
|
|
32
|
+
**For new .NET 8+ projects without existing tests:**
|
|
33
|
+
Offer the choice: "This is a new .NET 8+ project. I'll use **xUnit** (industry standard) by default. Would you prefer **TUnit** instead? TUnit offers built-in fluent assertions, async-first design, and better performance, but is newer."
|
|
34
|
+
|
|
35
|
+
**Note on FluentAssertions**: Version 8+ requires a commercial license ($130/dev/year). Avoid recommending it unless the project already uses it.
|
|
36
|
+
|
|
37
|
+
## Naming Conventions
|
|
38
|
+
|
|
39
|
+
Use `MethodName_Scenario_ExpectedBehavior` with PascalCase:
|
|
40
|
+
|
|
41
|
+
```csharp
|
|
42
|
+
// Pattern: MethodName_Scenario_ExpectedBehavior
|
|
43
|
+
ProcessOrder_WithValidOrder_ReturnsSuccess()
|
|
44
|
+
GetUser_WithNonExistentId_ThrowsUserNotFoundException()
|
|
45
|
+
CalculateDiscount_WhenOrderExceeds100_Returns10PercentOff()
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Patterns
|
|
49
|
+
|
|
50
|
+
### AAA with xUnit
|
|
51
|
+
|
|
52
|
+
```csharp
|
|
53
|
+
public class OrderServiceTests : IDisposable
|
|
54
|
+
{
|
|
55
|
+
private readonly Mock<IOrderRepository> _mockRepo;
|
|
56
|
+
private readonly FakeLogger<OrderService> _fakeLogger;
|
|
57
|
+
private readonly OrderService _sut;
|
|
58
|
+
|
|
59
|
+
public OrderServiceTests()
|
|
60
|
+
{
|
|
61
|
+
_mockRepo = new Mock<IOrderRepository>();
|
|
62
|
+
_fakeLogger = new FakeLogger<OrderService>();
|
|
63
|
+
_sut = new OrderService(_fakeLogger, _mockRepo.Object);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
[Fact]
|
|
67
|
+
public async Task ProcessOrder_WithValidOrder_ReturnsSuccess()
|
|
68
|
+
{
|
|
69
|
+
// Arrange
|
|
70
|
+
var order = CreateValidOrder();
|
|
71
|
+
_mockRepo.Setup(r => r.SaveAsync(It.IsAny<Order>()))
|
|
72
|
+
.ReturnsAsync(new Order { Id = "123" });
|
|
73
|
+
|
|
74
|
+
// Act
|
|
75
|
+
var result = await _sut.ProcessOrderAsync(order);
|
|
76
|
+
|
|
77
|
+
// Assert
|
|
78
|
+
Assert.True(result.IsSuccess);
|
|
79
|
+
Assert.Equal("123", result.Id);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public void Dispose() { /* cleanup if needed */ }
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### AAA with TUnit
|
|
87
|
+
|
|
88
|
+
```csharp
|
|
89
|
+
public class OrderServiceTests
|
|
90
|
+
{
|
|
91
|
+
private readonly Mock<IOrderRepository> _mockRepo = new();
|
|
92
|
+
private readonly OrderService _sut;
|
|
93
|
+
|
|
94
|
+
public OrderServiceTests()
|
|
95
|
+
{
|
|
96
|
+
_sut = new OrderService(_mockRepo.Object);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
[Test]
|
|
100
|
+
public async Task ProcessOrder_WithValidOrder_ReturnsSuccess()
|
|
101
|
+
{
|
|
102
|
+
// Arrange
|
|
103
|
+
var order = CreateValidOrder();
|
|
104
|
+
_mockRepo.Setup(r => r.SaveAsync(It.IsAny<Order>()))
|
|
105
|
+
.ReturnsAsync(new Order { Id = "123" });
|
|
106
|
+
|
|
107
|
+
// Act
|
|
108
|
+
var result = await _sut.ProcessOrderAsync(order);
|
|
109
|
+
|
|
110
|
+
// Assert — TUnit assertions are async and fluent
|
|
111
|
+
await Assert.That(result.IsSuccess).IsTrue();
|
|
112
|
+
await Assert.That(result.Id).IsEqualTo("123");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Parameterized Tests
|
|
118
|
+
|
|
119
|
+
```csharp
|
|
120
|
+
// xUnit
|
|
121
|
+
[Theory]
|
|
122
|
+
[InlineData(0, 100.0)]
|
|
123
|
+
[InlineData(10, 90.0)]
|
|
124
|
+
[InlineData(50, 50.0)]
|
|
125
|
+
public void ApplyDiscount_CalculatesCorrectly(int discount, double expected)
|
|
126
|
+
{
|
|
127
|
+
Assert.Equal(expected, ApplyDiscount(100.0, discount));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// TUnit
|
|
131
|
+
[Test]
|
|
132
|
+
[Arguments(0, 100.0)]
|
|
133
|
+
[Arguments(10, 90.0)]
|
|
134
|
+
[Arguments(50, 50.0)]
|
|
135
|
+
public async Task ApplyDiscount_CalculatesCorrectly(int discount, double expected)
|
|
136
|
+
{
|
|
137
|
+
await Assert.That(ApplyDiscount(100.0, discount)).IsEqualTo(expected);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Error Testing
|
|
142
|
+
|
|
143
|
+
```csharp
|
|
144
|
+
// xUnit exception testing
|
|
145
|
+
[Fact]
|
|
146
|
+
public async Task ProcessOrder_WithNullOrder_ThrowsArgumentNullException()
|
|
147
|
+
{
|
|
148
|
+
var exception = await Assert.ThrowsAsync<ArgumentNullException>(
|
|
149
|
+
() => _sut.ProcessOrderAsync(null!));
|
|
150
|
+
Assert.Equal("order", exception.ParamName);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// TUnit exception testing
|
|
154
|
+
[Test]
|
|
155
|
+
public async Task ProcessOrder_WithNullOrder_ThrowsArgumentNullException()
|
|
156
|
+
{
|
|
157
|
+
var action = () => _sut.ProcessOrderAsync(null!);
|
|
158
|
+
await Assert.That(action).ThrowsException()
|
|
159
|
+
.OfType<ArgumentNullException>();
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### FakeLogger for Logging Tests
|
|
164
|
+
|
|
165
|
+
```csharp
|
|
166
|
+
using Microsoft.Extensions.Logging.Testing;
|
|
167
|
+
|
|
168
|
+
var fakeLogger = new FakeLogger<OrderService>();
|
|
169
|
+
var sut = new OrderService(fakeLogger);
|
|
170
|
+
await sut.ProcessOrderAsync(orderId: 123);
|
|
171
|
+
|
|
172
|
+
var logEntry = fakeLogger.Collector.GetSnapshot()
|
|
173
|
+
.Single(r => r.Level == LogLevel.Information);
|
|
174
|
+
var state = logEntry.StructuredState!.ToDictionary(x => x.Key, x => x.Value);
|
|
175
|
+
Assert.Equal("123", state["OrderId"]);
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Mocking
|
|
179
|
+
|
|
180
|
+
### Moq (default)
|
|
181
|
+
|
|
182
|
+
```csharp
|
|
183
|
+
var mockRepo = new Mock<IOrderRepository>();
|
|
184
|
+
mockRepo.Setup(r => r.GetByIdAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
|
|
185
|
+
.ReturnsAsync(expectedDocument);
|
|
186
|
+
|
|
187
|
+
// Verify
|
|
188
|
+
mockRepo.Verify(r => r.SaveAsync(It.IsAny<Order>()), Times.Once);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### NSubstitute
|
|
192
|
+
|
|
193
|
+
```csharp
|
|
194
|
+
var repo = Substitute.For<IOrderRepository>();
|
|
195
|
+
repo.GetByIdAsync(Arg.Any<Guid>(), Arg.Any<CancellationToken>())
|
|
196
|
+
.Returns(expectedDocument);
|
|
197
|
+
|
|
198
|
+
// Verify
|
|
199
|
+
await repo.Received(1).SaveAsync(Arg.Any<Order>());
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### What NOT to mock
|
|
203
|
+
|
|
204
|
+
- Value objects, records, DTOs
|
|
205
|
+
- Pure static methods with no side effects
|
|
206
|
+
- The class under test itself
|
|
207
|
+
- Simple data structures
|
|
208
|
+
|
|
209
|
+
Mock only at system boundaries: repositories, external APIs, file system, clock.
|
|
210
|
+
|
|
211
|
+
## File Conventions
|
|
212
|
+
|
|
213
|
+
- `Tests/` or `*.Tests/` project mirroring source structure
|
|
214
|
+
- `*Tests.cs` suffix for test classes
|
|
215
|
+
- Constructor for per-test setup (xUnit creates new instance per test)
|
|
216
|
+
- `IDisposable` for teardown
|
|
217
|
+
- `dotnet test` to run
|
|
218
|
+
|
|
219
|
+
## Package Setup
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
# xUnit (default)
|
|
223
|
+
dotnet add package xunit
|
|
224
|
+
dotnet add package xunit.runner.visualstudio
|
|
225
|
+
dotnet add package Microsoft.NET.Test.Sdk
|
|
226
|
+
|
|
227
|
+
# TUnit (for .NET 8+ projects)
|
|
228
|
+
dotnet add package TUnit
|
|
229
|
+
|
|
230
|
+
# Mocking
|
|
231
|
+
dotnet add package Moq
|
|
232
|
+
# or
|
|
233
|
+
dotnet add package NSubstitute
|
|
234
|
+
|
|
235
|
+
# Logging testing
|
|
236
|
+
dotnet add package Microsoft.Extensions.Logging.Testing
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Authoritative Sources
|
|
240
|
+
|
|
241
|
+
- xUnit: https://xunit.net
|
|
242
|
+
- TUnit: https://github.com/thomhurst/TUnit
|
|
243
|
+
- NUnit: https://nunit.org
|
|
244
|
+
- Moq: https://github.com/moq/moq4
|
|
245
|
+
- NSubstitute: https://nsubstitute.github.io
|
|
246
|
+
- Kent Beck — Canon TDD: https://tidyfirst.substack.com/p/canon-tdd
|
|
247
|
+
- Martin Fowler — Mocks Aren't Stubs: https://martinfowler.com/articles/mocksArentStubs.html
|
|
248
|
+
|
|
249
|
+
## Reference Materials
|
|
250
|
+
|
|
251
|
+
- **[Anti-Patterns](reference/anti-patterns.md)** — Common testing mistakes and how to fix them
|
|
252
|
+
- **[TDD Workflow Patterns](reference/tdd-workflow-patterns.md)** — Red-Green-Refactor, Transformation Priority Premise, when to use TDD
|
|
@@ -12,6 +12,8 @@ Common testing mistakes that reduce test value and increase maintenance cost. Th
|
|
|
12
12
|
- [The Mockery](#the-mockery)
|
|
13
13
|
- [The Inspector](#the-inspector)
|
|
14
14
|
- [The Flaky Test](#the-flaky-test)
|
|
15
|
+
- [The Cargo Culter](#the-cargo-culter)
|
|
16
|
+
- [The Hard Test](#the-hard-test)
|
|
15
17
|
|
|
16
18
|
## The Liar
|
|
17
19
|
|
|
@@ -164,3 +166,79 @@ assertThat(result).isSortedAccordingTo(naturalOrder());
|
|
|
164
166
|
- Dependency on test execution order
|
|
165
167
|
|
|
166
168
|
**Fix:** Inject time as a dependency. Use fixed seeds for randomness. Ensure test isolation. Use proper async synchronization.
|
|
169
|
+
|
|
170
|
+
## The Cargo Culter
|
|
171
|
+
|
|
172
|
+
**What it is:** Writing tests to hit a coverage percentage target rather than to verify behavior. The tests exist to satisfy a metric, not to provide confidence.
|
|
173
|
+
|
|
174
|
+
**How to spot it:**
|
|
175
|
+
- Tests that assert trivially obvious things (e.g., `assert user.name == user.name`)
|
|
176
|
+
- Every private method has a corresponding test accessed via reflection
|
|
177
|
+
- 100% coverage but bugs still escape to production
|
|
178
|
+
- Test suite takes minutes to pass but developers don't trust it
|
|
179
|
+
|
|
180
|
+
**Fix:** Coverage is a diagnostic tool, not a goal. Use it to find untested gaps, not as a number to optimize. High 80s–90% emerges naturally from disciplined TDD. A test that only exists to push coverage up is worse than no test — it adds maintenance cost without adding confidence.
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
# Bad — written for coverage, not for confidence
|
|
184
|
+
def test_user_has_name():
|
|
185
|
+
user = User(name="Alice")
|
|
186
|
+
assert user.name is not None # This verifies nothing meaningful
|
|
187
|
+
|
|
188
|
+
# Good — written to verify a business rule
|
|
189
|
+
def test_user_with_empty_name_raises_validation_error():
|
|
190
|
+
with pytest.raises(ValidationError, match="name cannot be empty"):
|
|
191
|
+
User(name="")
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
> See: https://martinfowler.com/bliki/TestCoverage.html
|
|
195
|
+
|
|
196
|
+
## The Hard Test
|
|
197
|
+
|
|
198
|
+
**What it is:** Not an anti-pattern in the test itself, but a signal from the test about the production code. When a test is painful, complex, or requires elaborate setup, the production code has a design problem.
|
|
199
|
+
|
|
200
|
+
**How to spot it:**
|
|
201
|
+
- Need to mock 5+ dependencies to test one class
|
|
202
|
+
- Need to access private internals to verify behavior
|
|
203
|
+
- Test requires a complex sequence of operations just to get to the state under test
|
|
204
|
+
- You find yourself thinking "testing this would be too hard"
|
|
205
|
+
|
|
206
|
+
**What it signals:**
|
|
207
|
+
- Too many responsibilities in one class (SRP violation)
|
|
208
|
+
- Hidden dependencies or tight coupling
|
|
209
|
+
- Poor separation of concerns
|
|
210
|
+
- Untestable architecture (e.g., side effects embedded in business logic)
|
|
211
|
+
|
|
212
|
+
**Fix:** Resist the urge to skip the test or work around it with clever mocking. Instead, fix the production code design. Extract classes, inject dependencies, separate concerns. A hard test is a free design review — take the feedback.
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
# Hard to test — service does too much
|
|
216
|
+
class OrderService:
|
|
217
|
+
def process(self, order):
|
|
218
|
+
db = Database() # hidden dependency
|
|
219
|
+
email = EmailClient() # hidden dependency
|
|
220
|
+
self._validate(order)
|
|
221
|
+
db.save(order)
|
|
222
|
+
email.send_confirmation(order)
|
|
223
|
+
self._update_inventory(order) # another responsibility
|
|
224
|
+
|
|
225
|
+
# Easy to test — dependencies explicit, concerns separated
|
|
226
|
+
class OrderService:
|
|
227
|
+
def __init__(self, repo: OrderRepository, notifier: Notifier):
|
|
228
|
+
self._repo = repo
|
|
229
|
+
self._notifier = notifier
|
|
230
|
+
|
|
231
|
+
def process(self, order: Order) -> OrderResult:
|
|
232
|
+
self._validate(order)
|
|
233
|
+
saved = self._repo.save(order)
|
|
234
|
+
self._notifier.notify(saved)
|
|
235
|
+
return saved
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Further Reading
|
|
241
|
+
|
|
242
|
+
- xUnit Patterns (Meszaros): http://xunitpatterns.com
|
|
243
|
+
- Codepipes testing anti-patterns: https://blog.codepipes.com/testing/software-testing-antipatterns.html
|
|
244
|
+
- Google SWE Book — Test Doubles: https://abseil.io/resources/swe-book/html/ch13.html
|