Skip to content

Commit 2d74b61

Browse files
justin808claude
andauthored
Improve test assertions using explicit have_received checks (#854)
## Summary - Replace indirect `call_count` counter patterns with explicit RSpec spy assertions - Use `have_received(:capture3).with(hook_command)` to verify hook was called - Use `not_to have_received` to explicitly verify hook was skipped - Simplify mock setup by using direct return values where possible ## Why this matters The previous tests used indirect assertions like `expect(call_count).to eq(1)` which: - Don't explicitly prove what was or wasn't called - Are brittle to implementation changes - Require understanding the counter logic to interpret The new assertions like `expect(Open3).not_to have_received(:capture3).with(anything, hook_command, anything)` are: - Self-documenting - explicitly states "hook was NOT called" - More robust to implementation changes - Produce clearer failure messages ## Test plan - [ ] CI tests pass - [ ] Verify test output is meaningful when assertions fail Based on discussion: https://github.com/shakacode/shakapacker/discussions/new?category=ideas 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Simplified and hardened precompile hook tests using direct spy assertions, ensuring consistent verification across configured, unconfigured, skipped, and edge-case scenarios (spaces or env assignments) and verifying webpack runs independently of the hook. * **Documentation** * Updated testing guidance to recommend explicit spy assertions with examples and warn against indirect call-count patterns. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5c89f18 commit 2d74b61

File tree

2 files changed

+28
-35
lines changed

2 files changed

+28
-35
lines changed

CLAUDE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
- Run corresponding RSpec tests when changing source files
1313
- For example, when changing `lib/shakapacker/foo.rb`, run `spec/shakapacker/foo_spec.rb`
1414
- Run the full test suite with `bundle exec rspec` before pushing
15+
- **Use explicit RSpec spy assertions** - prefer `have_received`/`not_to have_received` over indirect counter patterns
16+
- Good: `expect(Open3).to have_received(:capture3).with(anything, hook_command, anything)`
17+
- Good: `expect(Open3).not_to have_received(:capture3).with(anything, hook_command, anything)`
18+
- Avoid: `call_count += 1` followed by `expect(call_count).to eq(1)`
1519

1620
## Code Style
1721

spec/shakapacker/compiler_spec.rb

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -63,26 +63,20 @@
6363

6464
hook_status = OpenStruct.new(success?: true, exitstatus: 0)
6565
webpack_status = OpenStruct.new(success?: true)
66+
hook_command = "bin/test-hook"
6667

67-
# Mock to track hook call and allow subsequent webpack call
68-
call_count = 0
6968
allow(Open3).to receive(:capture3) do |env, *args|
70-
call_count += 1
71-
if call_count == 1
72-
# First call: hook with executable as first arg
73-
expect(args[0]).to eq("bin/test-hook")
69+
if args[0] == hook_command
7470
["Hook output", "", hook_status]
7571
else
76-
# Second call: webpack
7772
["", "", webpack_status]
7873
end
7974
end
8075

81-
# Temporarily stub config to return hook
82-
allow(Shakapacker.config).to receive(:precompile_hook).and_return("bin/test-hook")
76+
allow(Shakapacker.config).to receive(:precompile_hook).and_return(hook_command)
8377

8478
expect(Shakapacker.compiler.compile).to be true
85-
expect(call_count).to eq(2) # Both hook and webpack were called
79+
expect(Open3).to have_received(:capture3).with(hash_including, hook_command, hash_including).once
8680
end
8781

8882
it "does not run precompile_hook when not configured" do
@@ -91,17 +85,14 @@
9185
allow(Shakapacker.compiler).to receive(:strategy).and_return(mocked_strategy)
9286

9387
webpack_status = OpenStruct.new(success?: true)
94-
call_count = 0
95-
allow(Open3).to receive(:capture3) do |*args|
96-
call_count += 1
97-
["", "", webpack_status]
98-
end
88+
allow(Open3).to receive(:capture3).and_return(["", "", webpack_status])
9989

10090
# Config returns nil for precompile_hook (default)
10191
expect(Shakapacker.config.precompile_hook).to be_nil
10292

10393
expect(Shakapacker.compiler.compile).to be true
104-
expect(call_count).to eq(1) # Only webpack was called
94+
# Verify webpack was called once, and no hook command was invoked
95+
expect(Open3).to have_received(:capture3).once
10596
end
10697

10798
it "raises error when precompile_hook fails" do
@@ -316,21 +307,21 @@
316307
allow(Shakapacker.compiler).to receive(:strategy).and_return(mocked_strategy)
317308

318309
webpack_status = OpenStruct.new(success?: true)
319-
call_count = 0
320-
allow(Open3).to receive(:capture3) do |*args|
321-
call_count += 1
322-
["", "", webpack_status]
323-
end
310+
hook_command = "bin/test-hook"
311+
allow(Open3).to receive(:capture3).and_return(["", "", webpack_status])
324312

325313
# Hook is configured
326-
allow(Shakapacker.config).to receive(:precompile_hook).and_return("bin/test-hook")
314+
allow(Shakapacker.config).to receive(:precompile_hook).and_return(hook_command)
327315

328316
# But skip flag is set
329317
allow(ENV).to receive(:[]).and_call_original
330318
allow(ENV).to receive(:[]).with("SHAKAPACKER_SKIP_PRECOMPILE_HOOK").and_return("true")
331319

332320
expect(Shakapacker.compiler.compile).to be true
333-
expect(call_count).to eq(1) # Only webpack was called, hook was skipped
321+
# Explicitly verify hook was NOT called
322+
expect(Open3).not_to have_received(:capture3).with(anything, hook_command, anything)
323+
# Verify webpack was still called
324+
expect(Open3).to have_received(:capture3).once
334325
end
335326

336327
it "runs precompile_hook when SHAKAPACKER_SKIP_PRECOMPILE_HOOK is not set" do
@@ -340,26 +331,25 @@
340331

341332
hook_status = OpenStruct.new(success?: true, exitstatus: 0)
342333
webpack_status = OpenStruct.new(success?: true)
334+
hook_command = "bin/test-hook"
343335

344-
call_count = 0
345336
allow(Open3).to receive(:capture3) do |env, *args|
346-
call_count += 1
347-
if call_count == 1
348-
expect(args[0]).to eq("bin/test-hook")
337+
if args[0] == hook_command
349338
["Hook output", "", hook_status]
350339
else
351340
["", "", webpack_status]
352341
end
353342
end
354343

355-
allow(Shakapacker.config).to receive(:precompile_hook).and_return("bin/test-hook")
344+
allow(Shakapacker.config).to receive(:precompile_hook).and_return(hook_command)
356345

357346
# Skip flag is not set
358347
allow(ENV).to receive(:[]).and_call_original
359348
allow(ENV).to receive(:[]).with("SHAKAPACKER_SKIP_PRECOMPILE_HOOK").and_return(nil)
360349

361350
expect(Shakapacker.compiler.compile).to be true
362-
expect(call_count).to eq(2) # Both hook and webpack were called
351+
# Explicitly verify hook was called
352+
expect(Open3).to have_received(:capture3).with(hash_including, hook_command, hash_including).once
363353
end
364354

365355
it "runs precompile_hook when SHAKAPACKER_SKIP_PRECOMPILE_HOOK is false" do
@@ -369,26 +359,25 @@
369359

370360
hook_status = OpenStruct.new(success?: true, exitstatus: 0)
371361
webpack_status = OpenStruct.new(success?: true)
362+
hook_command = "bin/test-hook"
372363

373-
call_count = 0
374364
allow(Open3).to receive(:capture3) do |env, *args|
375-
call_count += 1
376-
if call_count == 1
377-
expect(args[0]).to eq("bin/test-hook")
365+
if args[0] == hook_command
378366
["Hook output", "", hook_status]
379367
else
380368
["", "", webpack_status]
381369
end
382370
end
383371

384-
allow(Shakapacker.config).to receive(:precompile_hook).and_return("bin/test-hook")
372+
allow(Shakapacker.config).to receive(:precompile_hook).and_return(hook_command)
385373

386374
# Skip flag is set to "false" (not "true")
387375
allow(ENV).to receive(:[]).and_call_original
388376
allow(ENV).to receive(:[]).with("SHAKAPACKER_SKIP_PRECOMPILE_HOOK").and_return("false")
389377

390378
expect(Shakapacker.compiler.compile).to be true
391-
expect(call_count).to eq(2) # Both hook and webpack were called
379+
# Explicitly verify hook was called
380+
expect(Open3).to have_received(:capture3).with(hash_including, hook_command, hash_including).once
392381
end
393382
end
394383
end

0 commit comments

Comments
 (0)