Skip to content

Conversation

@justin808
Copy link
Member

@justin808 justin808 commented Dec 1, 2025

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

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.

✏️ Tip: You can customize this high-level summary in your review settings.

Replace indirect call_count counter patterns with explicit RSpec spy
assertions for verifying method invocations. This makes tests more
self-documenting and robust.

Changes:
- Use `have_received(:capture3).with(hook_command)` to explicitly
  verify hook was called
- Use `not_to have_received` to explicitly verify hook was skipped
- Remove call_count variables and conditional logic based on counts
- Simplify mock setup by using direct return values where possible

Benefits:
- Tests explicitly state what is being verified (hook called/skipped)
- More robust to implementation changes
- Clearer failure messages

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@coderabbitai
Copy link

coderabbitai bot commented Dec 1, 2025

Walkthrough

Tests for precompile-hook behavior were refactored to replace manual call-count tracking with explicit have_received spy assertions. Stubs for Open3.capture3 now dispatch based on the asserted hook command, and scenarios were updated for configured/absent hooks, skip-flag variations, and special-path cases.

Changes

Cohort / File(s) Change Summary
Test refactoring for hook assertion patterns
spec/shakapacker/compiler_spec.rb
Replaced manual call-count mechanism with direct have_received(...).with and .once assertions. Updated Open3.capture3 stubs to dispatch on the expected hook command value. Adjusted scenarios for precompile_hook configured/not, SKAPACKER_SKIP_PRECOMPILE_HOOK true/false, and hook paths containing spaces or env assignments.
Documentation: testing guidance
CLAUDE.md
Added guidance advocating explicit RSpec spy assertions (have_received / not_to have_received) over manual counter patterns, with examples and an anti-pattern note.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Focus areas during review:
    • Correctness of have_received assertions and hash_including argument matching
    • Open3.capture3 stub dispatch logic covers all tested command variants
    • Test coverage for skip-flag and special-path scenarios; ensure webpack invocation assertions remain independent

Possibly related issues

Possibly related PRs

Poem

A rabbit trims the test-run hedge, 🐰
Replaces counters with a sleeker pledge,
"have_received" now sings the tune,
Hooks verified by afternoon,
Hops away beneath the moon. ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: replacing indirect call-count patterns with explicit RSpec spy assertions using have_received checks in tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jg/explicit-test-checks

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef9fbce and 5675643.

📒 Files selected for processing (1)
  • CLAUDE.md (1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: shakacode/shakapacker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T05:14:43.766Z
Learning: Run corresponding RSpec tests when changing source files (e.g., when changing `lib/shakapacker/foo.rb`, run `spec/shakapacker/foo_spec.rb`)
📚 Learning: 2025-11-25T05:14:43.766Z
Learnt from: CR
Repo: shakacode/shakapacker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T05:14:43.766Z
Learning: Run corresponding RSpec tests when changing source files (e.g., when changing `lib/shakapacker/foo.rb`, run `spec/shakapacker/foo_spec.rb`)

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T05:14:43.766Z
Learnt from: CR
Repo: shakacode/shakapacker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T05:14:43.766Z
Learning: Run the full test suite with `bundle exec rspec` before pushing

Applied to files:

  • CLAUDE.md
📚 Learning: 2024-10-09T10:52:40.920Z
Learnt from: marvinthepa
Repo: shakacode/shakapacker PR: 520
File: spec/shakapacker/utils_manager_spec.rb:145-151
Timestamp: 2024-10-09T10:52:40.920Z
Learning: In tests, prefer some duplication if it makes the logic easier to follow. Avoid using RSpec shared contexts if they make tests unnecessarily complicated.

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T05:14:43.766Z
Learnt from: CR
Repo: shakacode/shakapacker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T05:14:43.766Z
Learning: Always run `bundle exec rubocop` before committing Ruby changes

Applied to files:

  • CLAUDE.md
📚 Learning: 2025-11-25T05:14:43.766Z
Learnt from: CR
Repo: shakacode/shakapacker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T05:14:43.766Z
Learning: Always use `bundle exec` prefix when running Ruby commands (rubocop, rspec, rake, etc.)

Applied to files:

  • CLAUDE.md
📚 Learning: 2024-10-09T10:50:16.512Z
Learnt from: marvinthepa
Repo: shakacode/shakapacker PR: 520
File: spec/shakapacker/utils_manager_spec.rb:104-116
Timestamp: 2024-10-09T10:50:16.512Z
Learning: In `spec/shakapacker/utils_manager_spec.rb`, `error_unless_package_manager_is_obvious` is not a test method, so extracting into a shared context may not be helpful.

Applied to files:

  • CLAUDE.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Testing (ubuntu-latest, 2.7, gemfiles/Gemfile-rails.6.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.2, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.4, gemfiles/Gemfile-rails.7.2.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.3, gemfiles/Gemfile-rails.8.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.4, gemfiles/Gemfile-rails.8.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.3, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.4, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.2, gemfiles/Gemfile-rails.8.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.2, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.2, gemfiles/Gemfile-rails.7.2.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.1, gemfiles/Gemfile-rails.7.2.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.0, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.1, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.1, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.0, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 2.7, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 2.7, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Test with RSpack
  • GitHub Check: test
  • GitHub Check: claude-review
🔇 Additional comments (2)
CLAUDE.md (2)

15-18: Great documentation update on explicit spy assertions.

The new guidance clearly explains the shift from indirect counter patterns to explicit RSpec spy assertions, with practical examples and a clear anti-pattern to avoid. This will help maintainers and contributors follow the improved testing approach demonstrated in the refactored test suite.


43-43: The file already ends with a trailing newline character and conforms to project guidelines.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link

claude bot commented Dec 1, 2025

Code Review

Overview

This PR improves test assertions by replacing indirect call_count counter patterns with explicit RSpec spy assertions. This is a positive refactoring that makes the test suite more maintainable and expressive.


✅ Strengths

  1. Improved Test Clarity: The new assertions like expect(Open3).to have_received(:capture3).with(hash_including, hook_command, hash_including).once are self-documenting and make it immediately clear what behavior is being verified.

  2. Better Negative Assertions: The addition of expect(Open3).not_to have_received(:capture3).with(anything, hook_command, anything) in the skip test is excellent - it explicitly proves the hook wasn't called, rather than relying on indirect call count logic.

  3. Simplified Mock Setup: Removing the call_count variable and associated conditionals reduces cognitive overhead when reading the tests.

  4. Consistent Pattern: The changes apply the same improvement pattern across multiple similar test cases (lines 59-80, 304-325, 327-353, 355-381).

  5. Code Reduction: Net reduction of 11 lines while improving clarity (24 additions, 35 deletions).


🔍 Observations & Suggestions

1. Inconsistent Refactoring

Several tests still use the call_count pattern and could benefit from similar refactoring:

  • Line 120-133: "handles hook with both stdout and stderr"
  • Line 176-192: "handles hook commands with spaces in paths using Shellwords"
  • Line 202-219: "warns when hook executable does not exist"
  • Line 249-272: "prevents shell injection via command chaining"
  • Line 282-302: "supports environment variable assignments in hook command"

Recommendation: For consistency and to maximize the benefits of this refactor, consider updating these remaining tests to use explicit have_received assertions as well. This would ensure the entire test suite follows the same pattern.

2. Minor: Assertion Specificity

In test at line 82-96 ("does not run precompile_hook when not configured"), the assertion:

expect(Open3).to have_received(:capture3).once

This verifies webpack was called but doesn't prove the hook wasn't called with a specific command. Consider adding a more explicit negative assertion like the one in the skip test.

3. Test Coverage

The changes don't affect test coverage - all existing scenarios remain tested. The refactoring is purely improving assertion quality. ✅


🔒 Security & Performance

  • No security concerns: Changes are test-only and don't affect production code.
  • No performance impact: Test execution performance should be similar or slightly better due to simplified mocking logic.

📋 Code Quality

  • Follows RSpec best practices: Using spy objects and have_received matchers is idiomatic RSpec
  • Consistent with project style: Changes align with existing patterns in the codebase
  • No linting issues expected: Code follows Ruby style conventions

🎯 Recommendations

  1. Approve and merge the current changes as they represent a clear improvement
  2. 🔄 Follow-up PR (optional): Consider refactoring the remaining 5 tests that still use call_count for complete consistency
  3. 📝 CHANGELOG: Based on project guidelines, this is a test-only refactoring and should NOT be added to CHANGELOG.md ✅

Final Verdict

Approved

This is a high-quality refactoring that improves test maintainability without changing behavior. The code is more readable, the assertions are more explicit, and future developers will have an easier time understanding and modifying these tests.

Great work on identifying and addressing this testing pattern improvement!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
spec/shakapacker/compiler_spec.rb (3)

112-133: Consider refactoring to use the new explicit assertion pattern.

This test still uses the call_count pattern (lines 120-128) and lacks explicit verification of what was called—it only checks that compile returns true. This is inconsistent with the PR objective to replace call_count with explicit have_received checks.

Consider refactoring to match the pattern used in lines 59-80:

     it "handles hook with both stdout and stderr" do
       mocked_strategy = spy("Strategy")
       allow(mocked_strategy).to receive(:stale?).and_return(true)
       allow(Shakapacker.compiler).to receive(:strategy).and_return(mocked_strategy)
 
       hook_status = OpenStruct.new(success?: true, exitstatus: 0)
       webpack_status = OpenStruct.new(success?: true)
+      hook_command = "bin/verbose-hook"
 
-      call_count = 0
-      allow(Open3).to receive(:capture3) do |*args|
-        call_count += 1
-        if call_count == 1
+      allow(Open3).to receive(:capture3) do |env, *args|
+        if args[0] == hook_command
           ["Standard output", "Warning message", hook_status]
         else
           ["", "", webpack_status]
         end
       end
 
-      allow(Shakapacker.config).to receive(:precompile_hook).and_return("bin/verbose-hook")
+      allow(Shakapacker.config).to receive(:precompile_hook).and_return(hook_command)
 
       expect(Shakapacker.compiler.compile).to be true
+      expect(Open3).to have_received(:capture3).with(hash_including, hook_command, hash_including).once
     end

168-192: Consider refactoring to use the new explicit assertion pattern.

This test still uses the call_count pattern and lacks explicit verification. For consistency with the PR objective, consider refactoring to use the hook_command dispatch pattern.

Note: Since this command includes arguments (--arg1 --arg2), you may need a more flexible verification approach. For example, you could verify the command executable was called:

expect(Open3).to have_received(:capture3) do |env, *args|
  expect(args[0]).to eq("bin/my script")
end

Or, if the implementation passes the command as a single string, the existing hash_including pattern might work. Based on learnings, run the corresponding RSpec test to verify the behavior.


194-219: Consider refactoring to use the new explicit assertion pattern.

This test still uses the call_count pattern (lines 202-210) and doesn't explicitly verify that the hook was called. For consistency with the PR objective, refactor to use the hook_command dispatch pattern similar to lines 59-80.

🧹 Nitpick comments (2)
spec/shakapacker/compiler_spec.rb (2)

241-272: Consider refactoring for consistency, though verification is present.

While this test does verify the hook was called (by checking captured_args), it still uses the call_count pattern. For consistency with the PR objective and other refactored tests, consider using the hook_command dispatch pattern.

You could refactor to capture args when detecting the hook command:

-      call_count = 0
       captured_args = []
       allow(Open3).to receive(:capture3) do |env, *args|
-        call_count += 1
-        captured_args << args if call_count == 1
-        if call_count == 1
+        if args[0] == "bin/prepare"
+          captured_args << args
           ["", "", hook_status]
         else
           ["", "", webpack_status]
         end
       end

Then add explicit verification:

expect(Open3).to have_received(:capture3).with(hash_including, "bin/prepare", any_args).once

274-302: Consider refactoring for consistency, though verification is present.

While this test verifies the hook was called with the correct environment variables (by checking captured_env), it still uses the call_count pattern. For consistency with the PR objective, consider refactoring to use the hook_command dispatch pattern.

You could refactor to:

-      call_count = 0
       captured_env = nil
       allow(Open3).to receive(:capture3) do |env, *args|
-        call_count += 1
-        captured_env = env if call_count == 1
-        if call_count == 1
+        if args[0] == "bin/hook"
+          captured_env = env
           ["", "", hook_status]
         else
           ["", "", webpack_status]
         end
       end
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c89f18 and ef9fbce.

📒 Files selected for processing (1)
  • spec/shakapacker/compiler_spec.rb (5 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-11-25T05:14:43.766Z
Learnt from: CR
Repo: shakacode/shakapacker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T05:14:43.766Z
Learning: Run corresponding RSpec tests when changing source files (e.g., when changing `lib/shakapacker/foo.rb`, run `spec/shakapacker/foo_spec.rb`)

Applied to files:

  • spec/shakapacker/compiler_spec.rb
📚 Learning: 2025-11-25T05:14:43.766Z
Learnt from: CR
Repo: shakacode/shakapacker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T05:14:43.766Z
Learning: This gem supports both webpack and rspack configurations - test changes with both bundlers when modifying core functionality

Applied to files:

  • spec/shakapacker/compiler_spec.rb
📚 Learning: 2024-10-09T10:50:16.512Z
Learnt from: marvinthepa
Repo: shakacode/shakapacker PR: 520
File: spec/shakapacker/utils_manager_spec.rb:104-116
Timestamp: 2024-10-09T10:50:16.512Z
Learning: In `spec/shakapacker/utils_manager_spec.rb`, `error_unless_package_manager_is_obvious` is not a test method, so extracting into a shared context may not be helpful.

Applied to files:

  • spec/shakapacker/compiler_spec.rb
📚 Learning: 2024-10-09T10:47:17.620Z
Learnt from: marvinthepa
Repo: shakacode/shakapacker PR: 520
File: spec/shakapacker/utils_manager_spec.rb:68-89
Timestamp: 2024-10-09T10:47:17.620Z
Learning: In `spec/shakapacker/utils_manager_spec.rb`, the code within the context `"when lockfile is in Rails.root, but pwd is different"` is not duplicated for each package manager.

Applied to files:

  • spec/shakapacker/compiler_spec.rb
📚 Learning: 2025-11-25T05:14:43.766Z
Learnt from: CR
Repo: shakacode/shakapacker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T05:14:43.766Z
Learning: Run the full test suite with `bundle exec rspec` before pushing

Applied to files:

  • spec/shakapacker/compiler_spec.rb
🧬 Code graph analysis (1)
spec/shakapacker/compiler_spec.rb (2)
lib/shakapacker/compiler.rb (1)
  • compile (19-36)
lib/shakapacker/configuration.rb (1)
  • precompile_hook (297-306)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Test with RSpack
  • GitHub Check: Testing (ubuntu-latest, 2.7, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: test
  • GitHub Check: Generator specs (ubuntu-latest, 3.4, gemfiles/Gemfile-rails.8.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.4, gemfiles/Gemfile-rails.7.2.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.1, gemfiles/Gemfile-rails.7.2.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.4, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.2, gemfiles/Gemfile-rails.8.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 2.7, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.3, gemfiles/Gemfile-rails.8.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.2, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.3, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.3, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 2.7, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.0, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.1, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.2, gemfiles/Gemfile-rails.7.0.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.2, gemfiles/Gemfile-rails.7.2.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.0, gemfiles/Gemfile-rails.7.1.x)
  • GitHub Check: Generator specs (ubuntu-latest, 3.1, gemfiles/Gemfile-rails.7.1.x)
🔇 Additional comments (5)
spec/shakapacker/compiler_spec.rb (5)

59-80: LGTM! Well-executed refactor to explicit assertions.

The new pattern with hook_command variable and dispatch-based stubbing makes the test much clearer. The explicit verification using have_received(:capture3).with(hash_including, hook_command, hash_including).once is self-documenting and will produce better failure messages than the old call_count approach.


82-96: LGTM! Appropriate verification for unconfigured hook scenario.

The simplified stub and verification correctly tests that when precompile_hook returns nil, only webpack is invoked. Checking once implicitly confirms the hook wasn't called, which is appropriate since there's no specific hook command to verify against.


304-325: LGTM! Excellent use of explicit negative assertion.

The explicit verification using not_to have_received(:capture3).with(anything, hook_command, anything) clearly documents that the hook should be skipped, while still verifying webpack was invoked. This is much more explicit and self-documenting than the old pattern.


327-353: LGTM! Consistent with the new assertion pattern.

The explicit verification clearly documents that the hook should be called when the skip flag is not set.


355-381: LGTM! Consistent with the new assertion pattern.

The explicit verification correctly tests that setting the skip flag to "false" (as opposed to "true") still runs the hook.

Document the preference for using `have_received`/`not_to have_received`
assertions over indirect counter patterns in tests.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@claude
Copy link

claude bot commented Dec 2, 2025

Code Review - PR #854

Overview

This PR improves test quality by replacing indirect assertion patterns with explicit RSpec spy assertions. The changes are well-aligned with the project's testing guidelines now documented in CLAUDE.md.

✅ Strengths

  1. Improved Test Clarity: The refactored tests are much more self-documenting. Compare:

    • Before: expect(call_count).to eq(2) (what was called?)
    • After: expect(Open3).to have_received(:capture3).with(hash_including, hook_command, hash_including).once (explicitly shows hook was called)
  2. Better Failure Messages: When tests fail, the new assertions will provide clearer error messages about what was or wasn't called.

  3. Reduced Test Fragility: Tests no longer rely on call order counting, making them more robust to refactoring.

  4. Documentation Update: Adding the guideline to CLAUDE.md ensures future tests follow this pattern.

⚠️ Issues Found

1. Incomplete Refactoring (Medium Priority)

The PR only refactors 4 out of 9 tests that use the call_count pattern. The following tests still use the anti-pattern:

  • Line 112: "handles hook with both stdout and stderr"
  • Line 168: "handles hook commands with spaces in paths using Shellwords"
  • Line 194: "supports hooks with additional arguments"
  • Line 241: "passes configured environment variables to precompile_hook"
  • Line 274: "merges hook environment variables with Rails.application.config_for"

Recommendation: Either refactor all tests in this PR for consistency, or add a follow-up task to complete the refactoring.

2. Inconsistent Mock Setup (Low Priority)

In spec/shakapacker/compiler_spec.rb:88, the mock uses .and_return(["", "", webpack_status]) (simple return), while other tests use a block with conditional logic. This inconsistency is actually fine since this test doesn't need to distinguish between hook and webpack calls, but it creates two different patterns in the test suite.

Current behavior:

  • Tests that need to differentiate calls: Use conditional blocks
  • Tests where hooks aren't called: Use simple and_return

This is reasonable, but worth noting for maintainability.

3. Potential False Positive in Skip Test (Low Priority)

In the "skips precompile_hook when SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true" test (line 304-325), the assertion:

expect(Open3).not_to have_received(:capture3).with(anything, hook_command, anything)
expect(Open3).to have_received(:capture3).once

This could pass even if the hook was called, as long as the arguments don't match. Consider making the mock setup more explicit by using a block that can verify the hook is truly skipped, similar to other refactored tests.

📋 Minor Observations

  1. Removed Comments: Some helpful comments were removed (e.g., "First call: hook with executable as first arg"). While the new code is more self-documenting, consider if any comments added value for complex test scenarios.

  2. Test Coverage: All modified tests appear to have the same coverage as before. Good!

  3. Code Style: Changes follow project conventions consistently.

🔒 Security

No security concerns. The changes are test-only and don't affect runtime behavior.

⚡ Performance

No performance impact. Changes are isolated to test suite.

🧪 Testing

  • ✅ Changes are in test files only
  • ✅ The refactored assertions test the same behavior
  • ⚠️ Verify CI passes to ensure refactored tests work correctly

Recommendations

Before merging:

  1. Complete the refactoring - Update the remaining 5 tests that still use call_count pattern for consistency
  2. Verify test failures - Intentionally break the code to ensure new assertions produce clear error messages (mentioned in PR test plan)

Nice to have:

  • Add an example of a "bad" test with call_count to CLAUDE.md so developers understand what to avoid

Verdict

Good improvement to test quality, but incomplete. The PR should either:

  • Refactor all 9 tests with call_count pattern, OR
  • Explain why only these 4 tests were selected and create a follow-up issue for the remaining 5

Overall: Approve with changes requested ✏️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants