@grimoire-cc/cli 0.13.2 → 0.14.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.
Files changed (50) hide show
  1. package/dist/commands/update.d.ts.map +1 -1
  2. package/dist/commands/update.js +14 -0
  3. package/dist/commands/update.js.map +1 -1
  4. package/dist/enforce.d.ts +3 -1
  5. package/dist/enforce.d.ts.map +1 -1
  6. package/dist/enforce.js +18 -6
  7. package/dist/enforce.js.map +1 -1
  8. package/dist/setup.d.ts.map +1 -1
  9. package/dist/setup.js +47 -0
  10. package/dist/setup.js.map +1 -1
  11. package/dist/summary.d.ts.map +1 -1
  12. package/dist/summary.js +9 -0
  13. package/dist/summary.js.map +1 -1
  14. package/package.json +1 -1
  15. package/packs/dev-pack/agents/grimoire.tdd-specialist.md +194 -27
  16. package/packs/dev-pack/grimoire.json +9 -42
  17. package/packs/dev-pack/skills/grimoire.conventional-commit/SKILL.md +68 -47
  18. package/packs/dotnet-pack/agents/grimoire.csharp-coder.md +110 -113
  19. package/packs/dotnet-pack/grimoire.json +23 -5
  20. package/packs/dotnet-pack/skills/grimoire.unit-testing-dotnet/SKILL.md +252 -0
  21. package/packs/{dev-pack/skills/grimoire.tdd-specialist → dotnet-pack/skills/grimoire.unit-testing-dotnet}/reference/anti-patterns.md +78 -0
  22. package/packs/dotnet-pack/skills/grimoire.unit-testing-dotnet/reference/tdd-workflow-patterns.md +259 -0
  23. package/packs/go-pack/grimoire.json +19 -0
  24. package/packs/go-pack/skills/grimoire.unit-testing-go/SKILL.md +256 -0
  25. package/packs/go-pack/skills/grimoire.unit-testing-go/reference/anti-patterns.md +244 -0
  26. package/packs/go-pack/skills/grimoire.unit-testing-go/reference/tdd-workflow-patterns.md +259 -0
  27. package/packs/python-pack/grimoire.json +19 -0
  28. package/packs/python-pack/skills/grimoire.unit-testing-python/SKILL.md +239 -0
  29. package/packs/python-pack/skills/grimoire.unit-testing-python/reference/anti-patterns.md +244 -0
  30. package/packs/python-pack/skills/grimoire.unit-testing-python/reference/tdd-workflow-patterns.md +259 -0
  31. package/packs/rust-pack/grimoire.json +29 -0
  32. package/packs/rust-pack/skills/grimoire.unit-testing-rust/SKILL.md +243 -0
  33. package/packs/rust-pack/skills/grimoire.unit-testing-rust/reference/anti-patterns.md +244 -0
  34. package/packs/rust-pack/skills/grimoire.unit-testing-rust/reference/tdd-workflow-patterns.md +259 -0
  35. package/packs/ts-pack/agents/grimoire.typescript-coder.md +36 -1
  36. package/packs/ts-pack/grimoire.json +27 -1
  37. package/packs/ts-pack/skills/grimoire.unit-testing-typescript/SKILL.md +255 -0
  38. package/packs/ts-pack/skills/grimoire.unit-testing-typescript/reference/anti-patterns.md +244 -0
  39. package/packs/ts-pack/skills/grimoire.unit-testing-typescript/reference/tdd-workflow-patterns.md +259 -0
  40. package/packs/dev-pack/skills/grimoire.tdd-specialist/SKILL.md +0 -248
  41. package/packs/dev-pack/skills/grimoire.tdd-specialist/reference/language-frameworks.md +0 -388
  42. package/packs/dev-pack/skills/grimoire.tdd-specialist/reference/tdd-workflow-patterns.md +0 -135
  43. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/SKILL.md +0 -293
  44. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/anti-patterns.md +0 -329
  45. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/framework-guidelines.md +0 -361
  46. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/parameterized-testing.md +0 -378
  47. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/test-organization.md +0 -476
  48. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/reference/test-performance.md +0 -576
  49. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/templates/tunit-template.md +0 -438
  50. package/packs/dotnet-pack/skills/grimoire.dotnet-unit-testing/templates/xunit-template.md +0 -303
@@ -0,0 +1,256 @@
1
+ ---
2
+ name: grimoire.unit-testing-go
3
+ description: "Go unit testing specialist. Patterns and best practices for the testing stdlib, testify, and gomock. Use when writing tests for .go files, table-driven tests, or asking about Go testing patterns, test helpers, mocking interfaces, benchmarks."
4
+ ---
5
+
6
+ # Go Unit Testing
7
+
8
+ Focused guidance for writing clean, idiomatic unit tests in Go projects.
9
+
10
+ ## Framework Selection
11
+
12
+ ### Detection
13
+
14
+ 1. Check existing test files first — always match what the project uses
15
+ 2. Check `go.mod` for `testify`, `gomock`, `mockery` dependencies
16
+ 3. Check existing `*_test.go` files for assertion style
17
+
18
+ ### Decision Table
19
+
20
+ | Condition | Use | Reason |
21
+ |-----------|-----|--------|
22
+ | Project has existing tests | **Match existing** | Consistency is paramount |
23
+ | New project, standard needs | **testing** (stdlib) | Built-in, zero dependencies |
24
+ | Need rich assertions + mocking | **testify** | `assert`, `require`, `mock` packages |
25
+ | Strict interface mocking | **gomock** | Code generation, strict expectations |
26
+ | User explicitly requests | **Requested** | Respect user preference |
27
+
28
+ ## Naming Conventions
29
+
30
+ Use `TestMethod_Scenario_Expected` with PascalCase/underscores:
31
+
32
+ ```go
33
+ // Pattern: TestMethod_Scenario_Expected
34
+ func TestGetUser_WithInvalidID_ReturnsNotFound(t *testing.T) { ... }
35
+ func TestCalculateTotal_WithDiscount_AppliesPercentage(t *testing.T) { ... }
36
+ func TestParseConfig_WithMissingFields_ReturnsError(t *testing.T) { ... }
37
+ ```
38
+
39
+ ## Patterns
40
+
41
+ ### AAA with testify
42
+
43
+ ```go
44
+ func TestProcessOrder_WithValidOrder_ReturnsID(t *testing.T) {
45
+ // Arrange
46
+ repo := new(MockOrderRepository)
47
+ repo.On("Save", mock.Anything).Return(&Order{ID: "123"}, nil)
48
+ service := NewOrderService(repo)
49
+
50
+ // Act
51
+ result, err := service.ProcessOrder(context.Background(), validOrder)
52
+
53
+ // Assert
54
+ require.NoError(t, err)
55
+ assert.Equal(t, "123", result.ID)
56
+ repo.AssertExpectations(t)
57
+ }
58
+
59
+ func TestProcessOrder_WithInvalidOrder_ReturnsError(t *testing.T) {
60
+ // Arrange
61
+ service := NewOrderService(nil)
62
+
63
+ // Act
64
+ _, err := service.ProcessOrder(context.Background(), invalidOrder)
65
+
66
+ // Assert
67
+ assert.ErrorIs(t, err, ErrValidation)
68
+ }
69
+ ```
70
+
71
+ ### Table-Driven Tests (Go idiom)
72
+
73
+ ```go
74
+ func TestApplyDiscount(t *testing.T) {
75
+ tests := []struct {
76
+ name string
77
+ price float64
78
+ discount int
79
+ expected float64
80
+ }{
81
+ {"no discount", 100.0, 0, 100.0},
82
+ {"10 percent", 100.0, 10, 90.0},
83
+ {"50 percent", 100.0, 50, 50.0},
84
+ }
85
+ for _, tt := range tests {
86
+ t.Run(tt.name, func(t *testing.T) {
87
+ result := ApplyDiscount(tt.price, tt.discount)
88
+ assert.Equal(t, tt.expected, result)
89
+ })
90
+ }
91
+ }
92
+ ```
93
+
94
+ ### Table-Driven with Error Cases
95
+
96
+ ```go
97
+ func TestValidateOrder(t *testing.T) {
98
+ tests := []struct {
99
+ name string
100
+ order Order
101
+ wantErr error
102
+ }{
103
+ {"valid order", validOrder(), nil},
104
+ {"empty items", Order{Items: nil}, ErrEmptyItems},
105
+ {"negative total", Order{Total: -1}, ErrNegativeTotal},
106
+ }
107
+ for _, tt := range tests {
108
+ t.Run(tt.name, func(t *testing.T) {
109
+ err := ValidateOrder(tt.order)
110
+ if tt.wantErr != nil {
111
+ assert.ErrorIs(t, err, tt.wantErr)
112
+ } else {
113
+ assert.NoError(t, err)
114
+ }
115
+ })
116
+ }
117
+ }
118
+ ```
119
+
120
+ ### Test Helpers
121
+
122
+ ```go
123
+ // t.Helper() marks function as a test helper — errors report caller's line
124
+ func newTestService(t *testing.T, opts ...func(*ServiceConfig)) *OrderService {
125
+ t.Helper()
126
+ cfg := defaultTestConfig()
127
+ for _, opt := range opts {
128
+ opt(cfg)
129
+ }
130
+ return NewOrderService(cfg)
131
+ }
132
+
133
+ // t.Cleanup() for automatic teardown
134
+ func setupTestDB(t *testing.T) *sql.DB {
135
+ t.Helper()
136
+ db, err := sql.Open("sqlite3", ":memory:")
137
+ require.NoError(t, err)
138
+ t.Cleanup(func() { db.Close() })
139
+ return db
140
+ }
141
+ ```
142
+
143
+ ### Subtests for Parallel Execution
144
+
145
+ ```go
146
+ func TestOrderService(t *testing.T) {
147
+ t.Run("ProcessOrder", func(t *testing.T) {
148
+ t.Run("with valid order", func(t *testing.T) {
149
+ t.Parallel()
150
+ // ... test body
151
+ })
152
+ t.Run("with invalid order", func(t *testing.T) {
153
+ t.Parallel()
154
+ // ... test body
155
+ })
156
+ })
157
+ }
158
+ ```
159
+
160
+ ### Error Testing
161
+
162
+ ```go
163
+ // Check specific error
164
+ func TestDivide_ByZero_ReturnsError(t *testing.T) {
165
+ _, err := Divide(1, 0)
166
+ assert.ErrorIs(t, err, ErrDivideByZero)
167
+ }
168
+
169
+ // Check error type
170
+ func TestParse_InvalidInput_ReturnsParseError(t *testing.T) {
171
+ _, err := Parse("bad input")
172
+ var parseErr *ParseError
173
+ assert.ErrorAs(t, err, &parseErr)
174
+ assert.Equal(t, "bad input", parseErr.Input)
175
+ }
176
+
177
+ // Check error message
178
+ func TestValidate_EmptyName_ReturnsErrorMessage(t *testing.T) {
179
+ err := Validate(User{Name: ""})
180
+ assert.EqualError(t, err, "name cannot be empty")
181
+ }
182
+ ```
183
+
184
+ ## Mocking
185
+
186
+ ### testify/mock
187
+
188
+ ```go
189
+ type MockOrderRepository struct {
190
+ mock.Mock
191
+ }
192
+
193
+ func (m *MockOrderRepository) Save(ctx context.Context, order *Order) (*Order, error) {
194
+ args := m.Called(ctx, order)
195
+ return args.Get(0).(*Order), args.Error(1)
196
+ }
197
+
198
+ // Usage
199
+ repo := new(MockOrderRepository)
200
+ repo.On("Save", mock.Anything, mock.Anything).Return(&Order{ID: "123"}, nil)
201
+ // ... use repo ...
202
+ repo.AssertExpectations(t)
203
+ ```
204
+
205
+ ### Interface-based fakes (preferred for simple cases)
206
+
207
+ ```go
208
+ // Define a minimal interface where you use it
209
+ type orderSaver interface {
210
+ Save(ctx context.Context, order *Order) (*Order, error)
211
+ }
212
+
213
+ // Fake implementation in test file
214
+ type fakeOrderSaver struct {
215
+ saved []*Order
216
+ err error
217
+ }
218
+
219
+ func (f *fakeOrderSaver) Save(_ context.Context, order *Order) (*Order, error) {
220
+ if f.err != nil {
221
+ return nil, f.err
222
+ }
223
+ f.saved = append(f.saved, order)
224
+ return &Order{ID: "123"}, nil
225
+ }
226
+ ```
227
+
228
+ ### What NOT to mock
229
+
230
+ - Value types, structs used as data containers
231
+ - Pure functions with no side effects
232
+ - The package under test itself
233
+ - Standard library types (use real `bytes.Buffer`, etc.)
234
+
235
+ Mock only at system boundaries: external APIs, databases, file system, time.
236
+
237
+ ## File Conventions
238
+
239
+ - `*_test.go` in the same package (white-box) or `_test` package (black-box)
240
+ - `go test ./...` to run all tests
241
+ - `testdata/` for test fixtures (ignored by Go tooling)
242
+ - `go test -race ./...` to detect race conditions
243
+ - `go test -cover ./...` for coverage
244
+
245
+ ## Authoritative Sources
246
+
247
+ - testing package: https://pkg.go.dev/testing
248
+ - testify: https://github.com/stretchr/testify
249
+ - gomock: https://github.com/uber-go/mock
250
+ - Kent Beck — Canon TDD: https://tidyfirst.substack.com/p/canon-tdd
251
+ - Martin Fowler — Mocks Aren't Stubs: https://martinfowler.com/articles/mocksArentStubs.html
252
+
253
+ ## Reference Materials
254
+
255
+ - **[Anti-Patterns](reference/anti-patterns.md)** — Common testing mistakes and how to fix them
256
+ - **[TDD Workflow Patterns](reference/tdd-workflow-patterns.md)** — Red-Green-Refactor, Transformation Priority Premise, when to use TDD
@@ -0,0 +1,244 @@
1
+ # Testing Anti-Patterns
2
+
3
+ Common testing mistakes that reduce test value and increase maintenance cost. These are language-agnostic — they apply to any test framework.
4
+
5
+ ## Table of Contents
6
+
7
+ - [The Liar](#the-liar)
8
+ - [The Giant](#the-giant)
9
+ - [Excessive Setup](#excessive-setup)
10
+ - [The Slow Poke](#the-slow-poke)
11
+ - [The Peeping Tom](#the-peeping-tom)
12
+ - [The Mockery](#the-mockery)
13
+ - [The Inspector](#the-inspector)
14
+ - [The Flaky Test](#the-flaky-test)
15
+ - [The Cargo Culter](#the-cargo-culter)
16
+ - [The Hard Test](#the-hard-test)
17
+
18
+ ## The Liar
19
+
20
+ **What it is:** A test that passes but doesn't actually verify the behavior it claims to test. It gives false confidence.
21
+
22
+ **How to spot it:**
23
+ - Test name says "validates input" but assertions only check the return type
24
+ - Assertions are too loose (`assert result is not None` instead of checking the actual value)
25
+ - Test catches exceptions broadly and passes regardless
26
+
27
+ **Fix:** Ensure assertions directly verify the specific behavior described in the test name. Every assertion should fail if the behavior breaks.
28
+
29
+ ```python
30
+ # Bad — passes even if discount logic is completely wrong
31
+ def test_apply_discount():
32
+ result = apply_discount(100, 10)
33
+ assert result is not None
34
+
35
+ # Good — fails if the calculation is wrong
36
+ def test_apply_discount_with_10_percent_returns_90():
37
+ result = apply_discount(100, 10)
38
+ assert result == 90.0
39
+ ```
40
+
41
+ ## The Giant
42
+
43
+ **What it is:** A single test that verifies too many things. When it fails, you can't tell which behavior broke.
44
+
45
+ **How to spot it:**
46
+ - Test has more than 8-10 assertions
47
+ - Test name uses "and" (e.g., "creates user and sends email and updates cache")
48
+ - Multiple Act phases in one test
49
+
50
+ **Fix:** Split into focused tests, each verifying one logical concept. Multiple assertions are fine if they verify aspects of the same behavior.
51
+
52
+ ```typescript
53
+ // Bad — three unrelated behaviors in one test
54
+ test('user registration works', () => {
55
+ const user = register({ name: 'Alice', email: 'alice@test.com' });
56
+ expect(user.id).toBeDefined();
57
+ expect(emailService.send).toHaveBeenCalled();
58
+ expect(cache.set).toHaveBeenCalledWith(`user:${user.id}`, user);
59
+ expect(auditLog.entries).toHaveLength(1);
60
+ });
61
+
62
+ // Good — separate tests for each behavior
63
+ test('register with valid data creates user with id', () => { ... });
64
+ test('register with valid data sends welcome email', () => { ... });
65
+ test('register with valid data caches the user', () => { ... });
66
+ test('register with valid data writes audit log entry', () => { ... });
67
+ ```
68
+
69
+ ## Excessive Setup
70
+
71
+ **What it is:** Tests that require dozens of lines of setup before the actual test logic. Often signals that the code under test has too many dependencies.
72
+
73
+ **How to spot it:**
74
+ - Arrange section is 20+ lines
75
+ - Multiple mocks configured with complex behaviors
76
+ - Shared setup methods that configure things most tests don't need
77
+
78
+ **Fix:** Use factory methods/builders for test data. Consider whether the code under test needs refactoring to reduce dependencies. Only set up what the specific test needs.
79
+
80
+ ```go
81
+ // Bad — every test sets up the entire world
82
+ func TestProcessOrder(t *testing.T) {
83
+ db := setupDatabase()
84
+ cache := setupCache()
85
+ logger := setupLogger()
86
+ emailClient := setupEmailClient()
87
+ validator := NewValidator(db)
88
+ processor := NewProcessor(cache)
89
+ service := NewOrderService(db, cache, logger, emailClient, validator, processor)
90
+ // ... 10 more lines of setup
91
+ result, err := service.ProcessOrder(ctx, order)
92
+ assert.NoError(t, err)
93
+ }
94
+
95
+ // Good — factory method hides irrelevant details
96
+ func TestProcessOrder_WithValidOrder_Succeeds(t *testing.T) {
97
+ service := newTestOrderService(t)
98
+ result, err := service.ProcessOrder(ctx, validOrder())
99
+ assert.NoError(t, err)
100
+ assert.Equal(t, "processed", result.Status)
101
+ }
102
+ ```
103
+
104
+ ## The Slow Poke
105
+
106
+ **What it is:** Tests that are slow because they use real I/O, network calls, or sleeps. Slow tests get run less frequently and slow down the feedback loop.
107
+
108
+ **How to spot it:**
109
+ - `time.Sleep()`, `Thread.sleep()`, `setTimeout` in tests
110
+ - Real HTTP calls, database connections, file system operations
111
+ - Test suite takes more than a few seconds for unit tests
112
+
113
+ **Fix:** Mock external dependencies. Use fake implementations for I/O. Replace time-based waits with event-based synchronization.
114
+
115
+ ## The Peeping Tom
116
+
117
+ **What it is:** Tests that access private/internal state to verify behavior instead of testing through the public interface.
118
+
119
+ **How to spot it:**
120
+ - Reflection to access private fields
121
+ - Testing internal method calls instead of observable results
122
+ - Assertions on implementation details (internal data structures, private counters)
123
+
124
+ **Fix:** Test through the public API. If you can't verify behavior through the public interface, the class may need a design change (e.g., expose a query method or extract a collaborator).
125
+
126
+ ## The Mockery
127
+
128
+ **What it is:** Tests that mock so heavily that they're testing mock configurations rather than real behavior. Every dependency is mocked, including simple value objects.
129
+
130
+ **How to spot it:**
131
+ - More mock setup lines than actual test logic
132
+ - Mocking concrete classes, value objects, or data structures
133
+ - Test passes but the real system fails because mocks don't match reality
134
+
135
+ **Fix:** Only mock at system boundaries (external services, databases, clocks). Use real implementations for in-process collaborators when practical.
136
+
137
+ ## The Inspector
138
+
139
+ **What it is:** Tests that verify exact method calls and their order rather than outcomes. They break whenever the implementation changes, even if behavior is preserved.
140
+
141
+ **How to spot it:**
142
+ - `verify(mock, times(1)).method()` for every mock interaction
143
+ - Assertions on call order
144
+ - Test breaks when you refactor without changing behavior
145
+
146
+ **Fix:** Verify state (the result) rather than interactions (how it got there). Only verify interactions for side effects that ARE the behavior (e.g., "email was sent").
147
+
148
+ ```java
149
+ // Bad — breaks if implementation changes sort algorithm
150
+ verify(sorter, times(1)).quickSort(any());
151
+ verify(sorter, never()).mergeSort(any());
152
+
153
+ // Good — verifies the outcome
154
+ assertThat(result).isSortedAccordingTo(naturalOrder());
155
+ ```
156
+
157
+ ## The Flaky Test
158
+
159
+ **What it is:** Tests that pass and fail intermittently without code changes. They erode trust in the test suite.
160
+
161
+ **Common causes:**
162
+ - Time-dependent logic (`new Date()`, `time.Now()`)
163
+ - Random data without fixed seeds
164
+ - Shared mutable state between tests
165
+ - Race conditions in async tests
166
+ - Dependency on test execution order
167
+
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