ace-test 0.6.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.
- checksums.yaml +7 -0
- data/.ace-defaults/nav/protocols/agent-sources/ace-test.yml +19 -0
- data/.ace-defaults/nav/protocols/guide-sources/ace-test.yml +19 -0
- data/.ace-defaults/nav/protocols/tmpl-sources/ace-test.yml +11 -0
- data/.ace-defaults/nav/protocols/wfi-sources/ace-test.yml +19 -0
- data/CHANGELOG.md +169 -0
- data/LICENSE +21 -0
- data/README.md +40 -0
- data/Rakefile +12 -0
- data/handbook/agents/mock.ag.md +164 -0
- data/handbook/agents/profile-tests.ag.md +132 -0
- data/handbook/agents/test.ag.md +99 -0
- data/handbook/guides/SUMMARY.md +95 -0
- data/handbook/guides/embedded-testing-guide.g.md +261 -0
- data/handbook/guides/mocking-patterns.g.md +464 -0
- data/handbook/guides/quick-reference.g.md +46 -0
- data/handbook/guides/test-driven-development-cycle/meta-documentation.md +26 -0
- data/handbook/guides/test-driven-development-cycle/ruby-application.md +18 -0
- data/handbook/guides/test-driven-development-cycle/ruby-gem.md +19 -0
- data/handbook/guides/test-driven-development-cycle/rust-cli.md +18 -0
- data/handbook/guides/test-driven-development-cycle/rust-wasm-zed.md +19 -0
- data/handbook/guides/test-driven-development-cycle/typescript-nuxt.md +18 -0
- data/handbook/guides/test-driven-development-cycle/typescript-vue.md +19 -0
- data/handbook/guides/test-layer-decision.g.md +261 -0
- data/handbook/guides/test-mocking-patterns.g.md +414 -0
- data/handbook/guides/test-organization.g.md +140 -0
- data/handbook/guides/test-performance.g.md +353 -0
- data/handbook/guides/test-responsibility-map.g.md +220 -0
- data/handbook/guides/test-review-checklist.g.md +231 -0
- data/handbook/guides/test-suite-health.g.md +337 -0
- data/handbook/guides/testable-code-patterns.g.md +315 -0
- data/handbook/guides/testing/ruby-rspec-config-examples.md +120 -0
- data/handbook/guides/testing/ruby-rspec.md +87 -0
- data/handbook/guides/testing/rust.md +52 -0
- data/handbook/guides/testing/test-maintenance.md +364 -0
- data/handbook/guides/testing/typescript-bun.md +47 -0
- data/handbook/guides/testing/vue-firebase-auth.md +546 -0
- data/handbook/guides/testing/vue-vitest.md +236 -0
- data/handbook/guides/testing-philosophy.g.md +82 -0
- data/handbook/guides/testing-strategy.g.md +151 -0
- data/handbook/guides/testing-tdd-cycle.g.md +146 -0
- data/handbook/guides/testing.g.md +170 -0
- data/handbook/skills/as-test-create-cases/SKILL.md +24 -0
- data/handbook/skills/as-test-fix/SKILL.md +26 -0
- data/handbook/skills/as-test-improve-coverage/SKILL.md +22 -0
- data/handbook/skills/as-test-optimize/SKILL.md +34 -0
- data/handbook/skills/as-test-performance-audit/SKILL.md +34 -0
- data/handbook/skills/as-test-plan/SKILL.md +34 -0
- data/handbook/skills/as-test-review/SKILL.md +34 -0
- data/handbook/skills/as-test-verify-suite/SKILL.md +45 -0
- data/handbook/templates/e2e-sandbox-checklist.template.md +289 -0
- data/handbook/templates/test-case.template.md +56 -0
- data/handbook/templates/test-performance-audit.template.md +132 -0
- data/handbook/templates/test-responsibility-map.template.md +92 -0
- data/handbook/templates/test-review-checklist.template.md +163 -0
- data/handbook/workflow-instructions/test/analyze-failures.wf.md +120 -0
- data/handbook/workflow-instructions/test/create-cases.wf.md +675 -0
- data/handbook/workflow-instructions/test/fix.wf.md +120 -0
- data/handbook/workflow-instructions/test/improve-coverage.wf.md +370 -0
- data/handbook/workflow-instructions/test/optimize.wf.md +368 -0
- data/handbook/workflow-instructions/test/performance-audit.wf.md +17 -0
- data/handbook/workflow-instructions/test/plan.wf.md +323 -0
- data/handbook/workflow-instructions/test/review.wf.md +16 -0
- data/handbook/workflow-instructions/test/verify-suite.wf.md +343 -0
- data/lib/ace/test/version.rb +7 -0
- data/lib/ace/test.rb +10 -0
- metadata +152 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc-type: guide
|
|
3
|
+
title: Mocking Patterns
|
|
4
|
+
purpose: Mocking and isolation patterns
|
|
5
|
+
ace-docs:
|
|
6
|
+
last-updated: 2026-01-23
|
|
7
|
+
last-checked: 2026-03-21
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Mocking Patterns
|
|
11
|
+
|
|
12
|
+
## Testing ENV-Dependent Classes
|
|
13
|
+
|
|
14
|
+
When testing classes that depend on environment variables, use the protected method pattern for parallel-safe, fast tests.
|
|
15
|
+
|
|
16
|
+
### Pattern: Protected Method for ENV Access
|
|
17
|
+
|
|
18
|
+
Instead of directly accessing ENV in your code, extract it to a protected method:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
class ProjectRootFinder
|
|
22
|
+
def find
|
|
23
|
+
# Check environment variable first
|
|
24
|
+
project_root_env = env_project_root
|
|
25
|
+
if project_root_env && !project_root_env.empty?
|
|
26
|
+
project_root = expand_path(project_root_env)
|
|
27
|
+
return project_root if Dir.exist?(project_root)
|
|
28
|
+
end
|
|
29
|
+
# ... fallback logic
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
protected
|
|
33
|
+
|
|
34
|
+
# Extract ENV access to allow test stubbing
|
|
35
|
+
def env_project_root
|
|
36
|
+
ENV['PROJECT_ROOT_PATH']
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Testing Without ENV Modification
|
|
42
|
+
|
|
43
|
+
Use method stubbing to test different ENV scenarios without modifying global state:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
def test_finds_project_without_env_variable
|
|
47
|
+
finder = ProjectRootFinder.new
|
|
48
|
+
# Stub env method to simulate no ENV variable
|
|
49
|
+
finder.stub :env_project_root, nil do
|
|
50
|
+
assert_equal expected_path, finder.find
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_uses_env_variable_when_set
|
|
55
|
+
finder = ProjectRootFinder.new
|
|
56
|
+
# Stub to simulate ENV variable being set
|
|
57
|
+
finder.stub :env_project_root, "/custom/path" do
|
|
58
|
+
assert_equal "/custom/path", finder.find
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def test_ignores_invalid_env_path
|
|
63
|
+
finder = ProjectRootFinder.new
|
|
64
|
+
# Stub to simulate invalid ENV path
|
|
65
|
+
finder.stub :env_project_root, "/nonexistent" do
|
|
66
|
+
# Should fall back to marker detection
|
|
67
|
+
assert_equal project_dir_with_git, finder.find
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Benefits
|
|
73
|
+
|
|
74
|
+
1. **Parallel-Safe**: No global ENV modification means tests can run in parallel
|
|
75
|
+
2. **Fast**: No subprocess spawning overhead (20x faster than subprocess approach)
|
|
76
|
+
3. **Clean**: Production code stays simple with just a protected method
|
|
77
|
+
4. **Complete**: Can test all ENV scenarios including presence, absence, and invalid values
|
|
78
|
+
|
|
79
|
+
### Anti-Pattern: Subprocess for ENV Testing
|
|
80
|
+
|
|
81
|
+
Avoid using subprocesses just to test ENV absence:
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
# DON'T DO THIS - Slow and complex
|
|
85
|
+
def test_without_env_slow
|
|
86
|
+
code = <<~RUBY
|
|
87
|
+
ENV.delete('MY_VAR')
|
|
88
|
+
obj = MyClass.new
|
|
89
|
+
puts obj.find
|
|
90
|
+
RUBY
|
|
91
|
+
|
|
92
|
+
output = run_in_subprocess(code)
|
|
93
|
+
assert_equal expected, output
|
|
94
|
+
end
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Each subprocess adds ~150ms overhead on typical systems.
|
|
98
|
+
|
|
99
|
+
## Subprocess Stubbing with Open3
|
|
100
|
+
|
|
101
|
+
When production code uses `Open3.capture3` for subprocess calls, stub it to avoid the ~150ms overhead:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
# Production code using subprocess
|
|
105
|
+
def execute_command(cmd, *args)
|
|
106
|
+
stdout, stderr, status = Open3.capture3(cmd, *args)
|
|
107
|
+
{ stdout: stdout, stderr: stderr, success: status.success? }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Test helper for stubbing Open3
|
|
111
|
+
def with_stubbed_subprocess(stdout: "", stderr: "", success: true)
|
|
112
|
+
mock_status = Object.new
|
|
113
|
+
mock_status.define_singleton_method(:success?) { success }
|
|
114
|
+
mock_status.define_singleton_method(:exitstatus) { success ? 0 : 1 }
|
|
115
|
+
|
|
116
|
+
Open3.stub :capture3, [stdout, stderr, mock_status] do
|
|
117
|
+
yield
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Usage in tests
|
|
122
|
+
def test_command_parsing
|
|
123
|
+
with_stubbed_subprocess(stdout: "expected output") do
|
|
124
|
+
result = MyCommand.execute("tool", "--flag")
|
|
125
|
+
assert result[:success]
|
|
126
|
+
assert_equal "expected output", result[:stdout]
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Prefer Higher-Level Stubs
|
|
132
|
+
|
|
133
|
+
Stub at the boundary closest to your test subject:
|
|
134
|
+
|
|
135
|
+
| Test Subject | Stub At | Not At |
|
|
136
|
+
|--------------|---------|--------|
|
|
137
|
+
| Command class | Command.execute | Open3.capture3 |
|
|
138
|
+
| Organism using Molecule | Molecule.call | Atom subprocess |
|
|
139
|
+
| CLI option parsing | API method | Subprocess |
|
|
140
|
+
| Integration test | Nothing (use real) | - |
|
|
141
|
+
|
|
142
|
+
### CLI Binary Testing Without Subprocess
|
|
143
|
+
|
|
144
|
+
Convert CLI subprocess tests to API tests when possible:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
# BEFORE: Slow subprocess test (~500ms)
|
|
148
|
+
def test_cli_flag
|
|
149
|
+
output, status = Open3.capture3("bin/ace-tool", "--verbose")
|
|
150
|
+
assert status.success?
|
|
151
|
+
assert_includes output, "Verbose mode"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# AFTER: Fast API test (~5ms)
|
|
155
|
+
def test_cli_flag
|
|
156
|
+
result = Ace::Tool.call(verbose: true)
|
|
157
|
+
assert result.success?
|
|
158
|
+
assert_includes result.output, "Verbose mode"
|
|
159
|
+
end
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## WebMock for HTTP API Mocking
|
|
163
|
+
|
|
164
|
+
Use WebMock (already in Gemfile) to intercept HTTP requests at the network level. This is ideal for testing code that calls external APIs (LLM providers, GitHub API, etc.) without making real network calls.
|
|
165
|
+
|
|
166
|
+
**Setup pattern:**
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
require "webmock/minitest"
|
|
170
|
+
|
|
171
|
+
class MyAPITest < Minitest::Test
|
|
172
|
+
def setup
|
|
173
|
+
super
|
|
174
|
+
WebMock.disable_net_connect!
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def teardown
|
|
178
|
+
WebMock.reset!
|
|
179
|
+
WebMock.allow_net_connect!
|
|
180
|
+
super
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Stubbing API responses:**
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
# Stub by URL pattern (regex)
|
|
189
|
+
def stub_google_api_success
|
|
190
|
+
stub_request(:post, /generativelanguage\.googleapis\.com/)
|
|
191
|
+
.to_return(
|
|
192
|
+
status: 200,
|
|
193
|
+
headers: { "Content-Type" => "application/json" },
|
|
194
|
+
body: {
|
|
195
|
+
"candidates" => [{ "content" => { "parts" => [{ "text" => "Mock response" }] } }],
|
|
196
|
+
"usageMetadata" => { "promptTokenCount" => 5, "candidatesTokenCount" => 10 }
|
|
197
|
+
}.to_json
|
|
198
|
+
)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Stub by exact URL
|
|
202
|
+
def stub_github_api_success
|
|
203
|
+
stub_request(:get, "https://api.github.com/repos/owner/repo")
|
|
204
|
+
.to_return(status: 200, body: { "id" => 123 }.to_json)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Stub error responses
|
|
208
|
+
def stub_api_error(status: 401)
|
|
209
|
+
stub_request(:any, /api\.example\.com/)
|
|
210
|
+
.to_return(status: status, body: { "error" => "Unauthorized" }.to_json)
|
|
211
|
+
end
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Usage in tests:**
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
def test_cli_routing_with_api_call
|
|
218
|
+
stub_google_api_success
|
|
219
|
+
with_real_config do
|
|
220
|
+
output = invoke_cli(["google:gemini-2.5-flash", "Hello"])
|
|
221
|
+
# Test verifies CLI routing, not API functionality
|
|
222
|
+
refute_match(/^Usage:/, output)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**When to use WebMock:**
|
|
228
|
+
- Tests that verify CLI argument routing (not API responses)
|
|
229
|
+
- Tests that need real config but mock network
|
|
230
|
+
- Cross-provider tests where mocking at HTTP level is simpler
|
|
231
|
+
|
|
232
|
+
**Existing usage:**
|
|
233
|
+
- `ace-git-secrets/test/atoms/service_api_client_test.rb` - GitHub API mocking
|
|
234
|
+
- `ace-llm/test/commands/query_command_test.rb` - LLM API mocking
|
|
235
|
+
|
|
236
|
+
**Performance:** WebMock stubs are instant (<1ms) vs real API calls (1-10+ seconds).
|
|
237
|
+
|
|
238
|
+
## Mock Git Repository Pattern
|
|
239
|
+
|
|
240
|
+
When testing code that interacts with git repositories, use `MockGitRepo` for fast unit tests instead of real git operations.
|
|
241
|
+
|
|
242
|
+
### When to Use MockGitRepo
|
|
243
|
+
|
|
244
|
+
- **Unit tests**: Testing file reading, validation, pattern matching
|
|
245
|
+
- **Tests that don't need git history**: Testing code that reads repo structure
|
|
246
|
+
- **Performance-critical tests**: Avoid ~150ms subprocess overhead per git command
|
|
247
|
+
|
|
248
|
+
### When to Use Real Repositories
|
|
249
|
+
|
|
250
|
+
- **Integration tests**: Testing actual git operations (commit, push, rebase)
|
|
251
|
+
- **E2E tests**: Verifying real tool execution (gitleaks, git-filter-repo)
|
|
252
|
+
- **Git history tests**: Testing log parsing, diff generation, branch operations
|
|
253
|
+
|
|
254
|
+
### Usage
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
# From ace-support-test-helpers
|
|
258
|
+
require 'ace/test_support'
|
|
259
|
+
|
|
260
|
+
class MyTest < Minitest::Test
|
|
261
|
+
def setup
|
|
262
|
+
@mock_repo = Ace::TestSupport::Fixtures::GitMocks::MockGitRepo.new
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def teardown
|
|
266
|
+
@mock_repo&.cleanup
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def test_file_processing
|
|
270
|
+
@mock_repo.add_file("config.yml", "key: value")
|
|
271
|
+
@mock_repo.add_commit("abc1234", message: "Add config")
|
|
272
|
+
|
|
273
|
+
result = MyProcessor.new(@mock_repo.path).process
|
|
274
|
+
assert result.success?
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def test_multiple_scenarios
|
|
278
|
+
# Test first scenario
|
|
279
|
+
@mock_repo.add_file("valid.txt", "content")
|
|
280
|
+
assert valid_result
|
|
281
|
+
|
|
282
|
+
# Reset and test second scenario
|
|
283
|
+
@mock_repo.reset!
|
|
284
|
+
@mock_repo.add_file("invalid.txt", "bad content")
|
|
285
|
+
refute valid_result
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Helper Pattern for Gem-Specific Tests
|
|
291
|
+
|
|
292
|
+
For gem-specific mocking (like gitleaks), create helpers that wrap the shared MockGitRepo:
|
|
293
|
+
|
|
294
|
+
```ruby
|
|
295
|
+
# In test_helper.rb
|
|
296
|
+
def with_mocked_git_repo
|
|
297
|
+
repo = Ace::TestSupport::Fixtures::GitMocks::MockGitRepo.new
|
|
298
|
+
begin
|
|
299
|
+
yield repo
|
|
300
|
+
ensure
|
|
301
|
+
repo.cleanup
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Thread-Safe Mocking
|
|
307
|
+
|
|
308
|
+
Use Minitest's `stub` method instead of `define_method` for thread-safe test mocking:
|
|
309
|
+
|
|
310
|
+
```ruby
|
|
311
|
+
# BAD - Not thread-safe, can cause race conditions
|
|
312
|
+
def with_mocked_scanner(result)
|
|
313
|
+
original = ScannerClass.instance_method(:scan)
|
|
314
|
+
ScannerClass.define_method(:scan) { result }
|
|
315
|
+
yield
|
|
316
|
+
ensure
|
|
317
|
+
ScannerClass.define_method(:scan, original)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# GOOD - Thread-safe stub pattern
|
|
321
|
+
def with_mocked_scanner(result)
|
|
322
|
+
ScannerClass.stub :new, ->(**_opts) {
|
|
323
|
+
mock = Object.new
|
|
324
|
+
mock.define_singleton_method(:scan) { result }
|
|
325
|
+
mock
|
|
326
|
+
} do
|
|
327
|
+
yield
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Benefits
|
|
333
|
+
|
|
334
|
+
1. **Speed**: MockGitRepo is ~150x faster than real git init
|
|
335
|
+
2. **Isolation**: No global state pollution between tests
|
|
336
|
+
3. **Determinism**: Predictable results without filesystem race conditions
|
|
337
|
+
4. **Portability**: Works in CI environments without git configuration
|
|
338
|
+
|
|
339
|
+
## Testing Classes with Multiple External Dependencies
|
|
340
|
+
|
|
341
|
+
For classes with multiple external dependencies (ENV, File, Time, etc.), apply the same pattern:
|
|
342
|
+
|
|
343
|
+
```ruby
|
|
344
|
+
class ConfigLoader
|
|
345
|
+
def load
|
|
346
|
+
config_path = env_config_path || default_config_path
|
|
347
|
+
return nil unless file_exists?(config_path)
|
|
348
|
+
|
|
349
|
+
content = read_file(config_path)
|
|
350
|
+
parse_with_timestamp(content, current_time)
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
protected
|
|
354
|
+
|
|
355
|
+
def env_config_path
|
|
356
|
+
ENV['CONFIG_PATH']
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def file_exists?(path)
|
|
360
|
+
File.exist?(path)
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def read_file(path)
|
|
364
|
+
File.read(path)
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def current_time
|
|
368
|
+
Time.now
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
This allows comprehensive stubbing in tests:
|
|
374
|
+
|
|
375
|
+
```ruby
|
|
376
|
+
def test_load_with_all_dependencies_stubbed
|
|
377
|
+
loader = ConfigLoader.new
|
|
378
|
+
|
|
379
|
+
loader.stub :env_config_path, "/custom/config.yml" do
|
|
380
|
+
loader.stub :file_exists?, true do
|
|
381
|
+
loader.stub :read_file, "key: value" do
|
|
382
|
+
loader.stub :current_time, Time.at(0) do
|
|
383
|
+
result = loader.load
|
|
384
|
+
assert_equal expected, result
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## DiffOrchestrator Stubbing Pattern
|
|
393
|
+
|
|
394
|
+
The `Ace::Git::Organisms::DiffOrchestrator` is used across multiple ACE packages. Proper stubbing prevents zombie mock issues and speeds up tests.
|
|
395
|
+
|
|
396
|
+
### Standard Stubbing Pattern
|
|
397
|
+
|
|
398
|
+
```ruby
|
|
399
|
+
# Helper for tests that need empty diff (most common case)
|
|
400
|
+
def with_empty_git_diff
|
|
401
|
+
empty_result = Ace::Git::Models::DiffResult.empty
|
|
402
|
+
Ace::Git::Organisms::DiffOrchestrator.stub(:generate, empty_result) do
|
|
403
|
+
yield
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# Usage
|
|
408
|
+
def test_document_status_without_changes
|
|
409
|
+
with_empty_git_diff do
|
|
410
|
+
result = DocumentAnalyzer.check_status(doc)
|
|
411
|
+
assert_equal :unchanged, result.status
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Three-Tier Git Testing Strategy
|
|
417
|
+
|
|
418
|
+
| Test Layer | Git Operations | Stub Level | Example |
|
|
419
|
+
|------------|---------------|------------|---------|
|
|
420
|
+
| Unit (atoms) | Full mock | MockGitRepo or inline stubs | Pattern parsing, validation |
|
|
421
|
+
| Unit (molecules) | Stub DiffOrchestrator | `with_empty_git_diff` | Document change detection |
|
|
422
|
+
| Unit (organisms) | Stub DiffOrchestrator | `with_mock_diff` | Business logic with diff |
|
|
423
|
+
| Integration | Real git operations | No stubbing | CLI parity, E2E workflows |
|
|
424
|
+
|
|
425
|
+
### Common Mistake: Stubbing Wrong Method
|
|
426
|
+
|
|
427
|
+
After refactoring, ensure mocks target the actual code path:
|
|
428
|
+
|
|
429
|
+
```ruby
|
|
430
|
+
# WRONG: Stubs method that no longer exists in code path
|
|
431
|
+
ChangeDetector.stub :execute_git_command, "" do
|
|
432
|
+
# Tests pass but run REAL git operations (zombie mock!)
|
|
433
|
+
result = ChangeDetector.get_diff_for_documents(docs)
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# CORRECT: Stubs actual method being called
|
|
437
|
+
Ace::Git::Organisms::DiffOrchestrator.stub :generate, empty_result do
|
|
438
|
+
# Fast, properly mocked test
|
|
439
|
+
result = ChangeDetector.get_diff_for_documents(docs)
|
|
440
|
+
end
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Cross-Package Usage
|
|
444
|
+
|
|
445
|
+
When your gem depends on ace-git, use DiffOrchestrator stubbing:
|
|
446
|
+
|
|
447
|
+
```ruby
|
|
448
|
+
# In ace-docs, ace-bundle, or any gem using git diffs
|
|
449
|
+
require 'ace/git'
|
|
450
|
+
|
|
451
|
+
def test_my_feature_with_git_dependency
|
|
452
|
+
with_empty_git_diff do
|
|
453
|
+
# Your test logic here - no real git operations
|
|
454
|
+
result = MyFeature.analyze(path)
|
|
455
|
+
assert result.valid?
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
## Related Guides
|
|
461
|
+
|
|
462
|
+
- [Testing Philosophy](guide://testing-philosophy) - Why IO isolation matters
|
|
463
|
+
- [Test Performance](guide://test-performance) - Performance targets and optimization
|
|
464
|
+
- [Testable Code Patterns](guide://testable-code-patterns) - Designing for testability
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc-type: guide
|
|
3
|
+
title: Testing Quick Reference
|
|
4
|
+
purpose: Testing quick reference
|
|
5
|
+
ace-docs:
|
|
6
|
+
last-updated: 2026-01-23
|
|
7
|
+
last-checked: 2026-03-21
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Testing Quick Reference
|
|
11
|
+
|
|
12
|
+
## TL;DR
|
|
13
|
+
|
|
14
|
+
- **Flat structure**: `test/atoms/`, `test/molecules/` - no deep nesting
|
|
15
|
+
- **Naming**: `*_test.rb` suffix, descriptive names
|
|
16
|
+
- **No IO in unit tests**: Use MockGitRepo, WebMock stubs, method stubbing
|
|
17
|
+
- **ENV testing**: Protected method pattern for parallel-safe tests
|
|
18
|
+
- **Fixtures**: YAML files in `test/fixtures/`, create via `yaml_fixture`
|
|
19
|
+
- **HTTP mocking**: VCR cassettes or WebMock stubs
|
|
20
|
+
- **File isolation**: `with_temp_dir` for filesystem tests
|
|
21
|
+
- **Run tests**: `ace-test atoms` or `ace-test path/to/test.rb`
|
|
22
|
+
|
|
23
|
+
## Performance Targets Quick View
|
|
24
|
+
|
|
25
|
+
| Test Layer | Target Time | Hard Limit |
|
|
26
|
+
|------------|-------------|------------|
|
|
27
|
+
| Unit (atoms) | <10ms | 50ms |
|
|
28
|
+
| Unit (molecules) | <50ms | 100ms |
|
|
29
|
+
| Unit (organisms) | <100ms | 200ms |
|
|
30
|
+
| Integration | <500ms | 1s |
|
|
31
|
+
| E2E | <2s | 5s |
|
|
32
|
+
|
|
33
|
+
## Key Patterns
|
|
34
|
+
|
|
35
|
+
- **E2E Rule**: Keep ONE E2E test per integration file, convert rest to mocked
|
|
36
|
+
- **Zombie Mocks**: Stubs that don't match actual code paths - profile regularly
|
|
37
|
+
- **Composite Helpers**: Reduce 6-7 level nesting to single helper calls
|
|
38
|
+
- **Sleep Stubbing**: Stub `Kernel.sleep` in retry tests
|
|
39
|
+
|
|
40
|
+
## Related Guides
|
|
41
|
+
|
|
42
|
+
- [Testing Philosophy](guide://testing-philosophy) - Pyramid, IO isolation
|
|
43
|
+
- [Test Organization](guide://test-organization) - Flat structure, naming
|
|
44
|
+
- [Mocking Patterns](guide://mocking-patterns) - Git, HTTP, subprocess, ENV
|
|
45
|
+
- [Test Performance](guide://test-performance) - Targets, optimization
|
|
46
|
+
- [Testable Code Patterns](guide://testable-code-patterns) - Status codes, exceptions
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc-type: guide
|
|
3
|
+
title: "Implementing Task Cycle: Meta (Documentation)"
|
|
4
|
+
purpose: Documentation workflow
|
|
5
|
+
ace-docs:
|
|
6
|
+
last-updated: 2026-01-23
|
|
7
|
+
last-checked: 2026-03-21
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Implementing Task Cycle: Meta (Documentation)
|
|
11
|
+
|
|
12
|
+
This details specific steps for the task cycle when the work involves modifying project documentation (guides, tasks,
|
|
13
|
+
research, etc.) within the ace-* packages.
|
|
14
|
+
|
|
15
|
+
1. Draft or update task/guide/research markdown file(s).
|
|
16
|
+
2. Run `bin/lint` (which includes link checking) and address any reported issues.
|
|
17
|
+
3. Commit the documentation changes using the [conventional commit format](../version-control-system-message.g.md).
|
|
18
|
+
4. Perform self-reflection on the documentation changes. Individual reflections can be captured using the [Create Reflection Note workflow](wfi://create-reflection-note). During this step:
|
|
19
|
+
* Review the documentation for clarity, completeness, and accuracy
|
|
20
|
+
* Consider if the documentation effectively communicates its intended purpose
|
|
21
|
+
* Identify any gaps or areas that could be improved
|
|
22
|
+
* Document insights or lessons learned during the documentation process
|
|
23
|
+
5. Commit any findings or documentation updates resulting from reflection.
|
|
24
|
+
6. If architecture changes were documented, run any scripts to update diagrams (e.g., `generate-blueprint` if applicable).
|
|
25
|
+
7. Push changes.
|
|
26
|
+
8. Mark the task as done.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc-type: guide
|
|
3
|
+
title: "Implementing Task Cycle: Ruby Application"
|
|
4
|
+
purpose: TDD workflow for Ruby applications
|
|
5
|
+
ace-docs:
|
|
6
|
+
last-updated: 2026-01-23
|
|
7
|
+
last-checked: 2026-03-21
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Implementing Task Cycle: Ruby Application
|
|
11
|
+
|
|
12
|
+
This details specific steps and commands for the task cycle when working on a Ruby application within this project.
|
|
13
|
+
|
|
14
|
+
1. **RSpec first**: `bundle exec rspec --only-failures` keeps focus on broken specs.
|
|
15
|
+
2. **Code & RuboCop**: Auto‑correct style (`bundle exec rubocop -A`), then rerun tests.
|
|
16
|
+
3. **Commit / Retrospect / Re‑commit** as per the [generic cycle](./testing-tdd-cycle.g.md).
|
|
17
|
+
4. **CI** → GitHub Action runs Ruby 3.2 & 3.3 matrix with `rspec` + `rubocop`.
|
|
18
|
+
5. **Deployment** handled by a separate release workflow.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc-type: guide
|
|
3
|
+
title: "Implementing Task Cycle: Ruby Gem"
|
|
4
|
+
purpose: TDD workflow for Ruby gems
|
|
5
|
+
ace-docs:
|
|
6
|
+
last-updated: 2026-01-23
|
|
7
|
+
last-checked: 2026-03-21
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Implementing Task Cycle: Ruby Gem
|
|
11
|
+
|
|
12
|
+
This details specific steps and commands for the task cycle when working on a Ruby gem within this project.
|
|
13
|
+
|
|
14
|
+
* Scaffold with `bundle gem my_gem`—comes with Rake tasks.
|
|
15
|
+
* Follow the standard [Test -> Code -> Refactor cycle](./testing-tdd-cycle.g.md) using
|
|
16
|
+
RSpec/RuboCop as in Ruby applications.
|
|
17
|
+
* After tests pass, `rake release` builds the gem and pushes to RubyGems (when not in a protected
|
|
18
|
+
branch).
|
|
19
|
+
* Docs generated with YARD; version bumped in `lib/my_gem/version.rb`.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc-type: guide
|
|
3
|
+
title: "Implementing Task Cycle: Rust CLI"
|
|
4
|
+
purpose: TDD workflow for Rust CLI apps
|
|
5
|
+
ace-docs:
|
|
6
|
+
last-updated: 2026-01-23
|
|
7
|
+
last-checked: 2026-03-21
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Implementing Task Cycle: Rust CLI
|
|
11
|
+
|
|
12
|
+
This details specific steps and commands for the task cycle when working on a Rust command-line application within this project.
|
|
13
|
+
|
|
14
|
+
* Follow the standard [Test -> Code -> Refactor cycle](./testing-tdd-cycle.g.md).
|
|
15
|
+
* Use `cargo test` for running tests.
|
|
16
|
+
* Use `cargo clippy --all-targets` for linting.
|
|
17
|
+
* Use `cargo fmt --check` (or `cargo fmt` to apply) for formatting.
|
|
18
|
+
* Matrix CI from GitHub template exercises stable/beta/nightly.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc-type: guide
|
|
3
|
+
title: "Implementing Task Cycle: Rust→Wasm Zed Extension"
|
|
4
|
+
purpose: TDD workflow for Zed extensions
|
|
5
|
+
ace-docs:
|
|
6
|
+
last-updated: 2026-01-23
|
|
7
|
+
last-checked: 2026-03-21
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Implementing Task Cycle: Rust→Wasm Zed Extension
|
|
11
|
+
|
|
12
|
+
This details specific steps and commands for the task cycle when developing Rust-based Zed editor
|
|
13
|
+
extensions compiled to Wasm.
|
|
14
|
+
|
|
15
|
+
1. Define the interface in `.wit`; run `wit_bindgen_rust` to generate bindings.
|
|
16
|
+
2. Implement logic following the [Test -> Code -> Refactor cycle](./testing-tdd-cycle.g.md).
|
|
17
|
+
3. Build with `cargo build --target wasm32-unknown-unknown --release`.
|
|
18
|
+
4. Optimize size with `wasm-snip` if needed.
|
|
19
|
+
5. Smoke‑test by loading the generated `.wasm` file into the Zed sandbox environment.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc-type: guide
|
|
3
|
+
title: "Implementing Task Cycle: TypeScript + Nuxt"
|
|
4
|
+
purpose: TDD workflow for Nuxt apps
|
|
5
|
+
ace-docs:
|
|
6
|
+
last-updated: 2026-01-23
|
|
7
|
+
last-checked: 2026-03-21
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Implementing Task Cycle: TypeScript + Nuxt
|
|
11
|
+
|
|
12
|
+
This details specific steps and commands for the task cycle when working on a TypeScript/Nuxt application.
|
|
13
|
+
|
|
14
|
+
* Follow the standard [Test -> Code -> Refactor cycle](./testing-tdd-cycle.g.md).
|
|
15
|
+
* Use `@nuxt/test-utils`. Opt‑in to Nuxt runtime tests with `.nuxt.spec.ts` file names or
|
|
16
|
+
`@vitest-environment nuxt` directive.
|
|
17
|
+
* Lint with ESLint/Prettier (`npm run lint`).
|
|
18
|
+
* Universal build (`nitro`) verified in CI; deployment handled by separate release pipeline.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
doc-type: guide
|
|
3
|
+
title: "Implementing Task Cycle: TypeScript + Vue"
|
|
4
|
+
purpose: TDD workflow for Vue apps
|
|
5
|
+
ace-docs:
|
|
6
|
+
last-updated: 2026-01-23
|
|
7
|
+
last-checked: 2026-03-21
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Implementing Task Cycle: TypeScript + Vue
|
|
11
|
+
|
|
12
|
+
This details specific steps and commands for the task cycle when working on a TypeScript/Vue frontend
|
|
13
|
+
application (e.g., bootstrapped with Vite).
|
|
14
|
+
|
|
15
|
+
* Project likely bootstrapped by Vite.
|
|
16
|
+
* Follow the standard [Test -> Code -> Refactor cycle](./testing-tdd-cycle.g.md).
|
|
17
|
+
* Use **Vitest** + `@vue/test-utils` for unit tests.
|
|
18
|
+
* Lint with ESLint & Prettier (`npm run lint`).
|
|
19
|
+
* CI runs `npm run lint && npm run test`.
|