Skip to content

refactor: tests container based mocking - #32

Merged
loadinglucian merged 8 commits into
mainfrom
refactor/tests-container-based-mocking
Oct 10, 2025
Merged

refactor: tests container based mocking#32
loadinglucian merged 8 commits into
mainfrom
refactor/tests-container-based-mocking

Conversation

@loadinglucian

@loadinglucian loadinglucian commented Oct 10, 2025

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Container now supports binding concrete instances for test-time overrides.
  • Refactor

    • Command construction moved to container-based assembly across tests, replacing manual wiring.
  • Tests

    • Updated integration and unit tests to use container setup.
    • Added tests covering binding behavior, override semantics, and data injection.
    • Replaced legacy test helper with a container-centric helper.
  • Documentation

    • Updated architecture and testing docs with examples for container bindings and test patterns.

Add bind() method to Container class to allow registering concrete
instances for testing. This enables dependency injection mocking while
maintaining the existing auto-wiring behavior for production code.

- Add bindings array to store registered instances
- Add bind() method with fluent interface
- Modify build() to check bindings before auto-wiring
- Maintain backward compatibility with existing usage
…iner

Replace the redundant mockTestConsoleCommand() function with a more
flexible mockCommandContainer() that creates a Container with all
BaseCommand dependencies pre-bound for testing.

- Remove mockTestConsoleCommand() function (35 lines eliminated)
- Add mockCommandContainer() with configurable service overrides
- Remove unused TestConsoleCommand import
- Add PrompterService import for proper type hints

This centralizes command testing setup and reduces maintenance burden
when adding new services to BaseCommand.
Replace manual dependency injection with container-based approach
using mockCommandContainer() for all server command tests.

- ServerAddCommandTest: 16 lines → 3 lines (81% reduction)
- ServerListCommandTest: 21 lines → 8 lines (62% reduction)
- ServerDeleteCommandTest: 21 lines → 8 lines (62% reduction)
- Remove unused Container and ServerRepository imports

This eliminates the need to manually pass all BaseCommand
dependencies, making tests more maintainable and consistent.
Update ConsoleInputTrait, ConsoleOutputTrait, and ServerHelpersTrait
tests to use the new mockCommandContainer() approach instead of
the deprecated mockTestConsoleCommand() function.

- Replace mockTestConsoleCommand() calls with container->build()
- Maintain same test behavior with cleaner setup
- Consistent with other command tests
Add tests for the new Container::bind() method to ensure proper
functionality and integration with existing auto-wiring system.

- Test basic binding and retrieval of instances
- Test bound instances override auto-wiring
- Test bound instances propagate through dependency chains
- Test fluent interface return value

Also update TestHelpersTest to replace mockTestConsoleCommand
tests with mockCommandContainer tests, including verification
of service override capabilities and inventory data handling.
@coderabbitai

coderabbitai Bot commented Oct 10, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a binding API to the DI Container (Container::bind) that lets tests register concrete instances; container build short-circuits to return bound instances. Test helpers and many tests are refactored to return/use a container (mockCommandContainer) and to build commands via the container with bound service overrides. Docs updated to show binding usage.

Changes

Cohort / File(s) Summary
Container binding API
app/Container.php
Adds private bindings map with /** @var array<class-string, object> */ annotation and a new public function bind(string $className, object $instance): self. build() now checks and returns bound instances before auto-wiring. Docblocks updated.
Test helper refactor to container-first
tests/TestHelpers.php
Replaces mockTestConsoleCommand(...) with mockCommandContainer(...): Container. Accepts optional typed service instances, binds default or provided services (EnvService, InventoryService, ServerRepository, SSHService, PrompterService, etc.) into the container, updates imports and examples.
Integration: server console commands via DI
tests/Integration/Console/Server/ServerAddCommandTest.php, tests/Integration/Console/Server/ServerDeleteCommandTest.php, tests/Integration/Console/Server/ServerListCommandTest.php
Refactors tests to use mockCommandContainer(...) then $container->build(Command::class) instead of manual constructor injection and explicit repository/inventory/SSH/prompter wiring. Adjusts assertions to rely on container-built command behavior.
Unit: container binding behavior
tests/Unit/ContainerTest.php
Adds unit tests for binding: fluent return, retrieval of bound instances, precedence over auto-wiring, and consistent returns for repeated builds.
Unit: test helpers usage and overrides
tests/Unit/TestHelpersTest.php
Updates tests to use mockCommandContainer flow; adds tests asserting container-built commands, service overrides (e.g., SSHService) via bind, and inventory data overrides reflected in repository.
Unit: console trait tests via DI
tests/Unit/Traits/ConsoleInputTraitTest.php, tests/Unit/Traits/ConsoleOutputTraitTest.php, tests/Unit/Traits/ServerHelpersTraitTest.php
beforeEach setups now create mockCommandContainer() and $container->build(TestConsoleCommand::class) instead of mockTestConsoleCommand(). No test logic changes beyond instantiation path.
Docs: testing & architecture guidance
.cursor/rules/01-architecture.mdc, .cursor/rules/02-tests.mdc
Adds docs describing Container::bind for test mocking, updates examples to show mockCommandContainer, service overrides, and pre-populated inventory data, and updates helper signature examples to include new optional services.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Tester
  participant C as Container
  participant B as Bindings Map
  participant R as Resolver
  participant Cmd as Command

  Tester->>C: bind(ServiceClass, mockedInstance)
  Note right of C: store bound instance
  Tester->>C: build(CommandClass)
  C->>B: has(CommandClass)?
  alt Bound
    B-->>C: return bound instance
    C-->>Tester: Command instance (from bindings)
  else Not bound
    C->>R: resolve(CommandClass)
    R->>C: construct with deps (using bindings if present)
    C-->>Tester: Command instance
  end
Loading
sequenceDiagram
  autonumber
  actor Tester
  participant C as Container
  participant B as Bindings Map
  participant R as Resolver
  participant Repo as ServerRepository
  participant Cmd as ServerDeleteCommand

  Tester->>C: bind(ServerRepository, TestRepo)
  Tester->>C: build(ServerDeleteCommand)
  C->>B: has(ServerDeleteCommand)?
  alt Not bound
    C->>R: resolve(ServerDeleteCommand)
    R->>C: inject(ServerRepository = TestRepo)
    C-->>Tester: ServerDeleteCommand (with TestRepo)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • bigpixelrocket/deployer-php#17 — Related earlier shift toward container-based command construction and test helper changes; overlaps on test helper and docs edits.
  • bigpixelrocket/deployer-php#27 — Related refactor of test helper wiring for console commands and adopting container-driven construction.

Poem

A hop, a bind, a gentle nudge to flow,
I weave my warren where dependencies grow.
Commands now sprout from containers so neat,
With mocked little carrots for services sweet.
Bound burrows guide the tests through the night—thump! 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title accurately and concisely summarizes the primary change, which is refactoring the test suite to use container-based mocking in place of manual dependency wiring.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/tests-container-based-mocking

📜 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 b51bed7 and 2cd0fa6.

📒 Files selected for processing (5)
  • .cursor/rules/01-architecture.mdc (1 hunks)
  • .cursor/rules/02-tests.mdc (1 hunks)
  • tests/Integration/Console/Server/ServerAddCommandTest.php (3 hunks)
  • tests/TestHelpers.php (2 hunks)
  • tests/Unit/ContainerTest.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.php

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

**/*.php: Eliminate single-use private methods by inlining them directly
Cache expensive computed values by initializing them in the constructor instead of recomputing
Prefer direct property access over method calls when appropriate to avoid call overhead
Organize code into comment-separated sections; prefer alphabetical ordering when it does not conflict with logical grouping

**/*.php: Adhere to PSR-12 for all PHP code
Declare strict_types and write code assuming strict typing
Prefer PHP 8.x language features where appropriate (union types, match, attributes, readonly)
Use import statements (use ...) instead of fully qualified class names in code
All methods must have explicit return types, and use proper generics in types (e.g., Collection<int, User>)
Use dependency injection instead of manually resolving or instantiating classes
Prefer Symfony component classes (e.g., Filesystem, Process) over native PHP functions for testability
All object creation must use $container->build(ClassName::class), except value objects, DTOs, and pure data structures
In production code, access the container via constructor injection (no service locator or global access)
Add DocBlock comments with minimalist descriptions, parameters, and return types for classes and functions
Use comments as visual separators with section headers/subheaders and avoid obvious or stale comments

Files:

  • tests/Unit/ContainerTest.php
  • tests/TestHelpers.php
  • tests/Integration/Console/Server/ServerAddCommandTest.php
{tests/**,test/**,**/*@(Test|Spec).php}

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

Do not run or edit tests unless explicitly instructed

Files:

  • tests/Unit/ContainerTest.php
  • tests/TestHelpers.php
  • tests/Integration/Console/Server/ServerAddCommandTest.php
tests/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

tests/**/*.php: In tests, it's allowed to instantiate the Container directly for isolation
Exclude tests from PHPStan static analysis

tests/**/*.php: Write Pest tests using it() syntax
In unit tests, prefer manual instantiation with explicit mocks (e.g., new ClassName(...)) over using the DI container
Use the DI container only in integration tests when verifying multiple services together or DI configuration
DI container conventions for production do not apply to tests
Apply the AAA pattern (Arrange, Act, Assert) in every test
For exception tests, combine phases with a // ACT & ASSERT comment when the act triggers the assertion
Organize tests with describe() blocks and shared setup via beforeEach(); extract helpers/traits for DRY tests
Keep test files under 1.8x the size of the source they test, without sacrificing readability
Focus tests on core business logic; avoid testing the framework itself
Use dataset-driven testing with ->with([]) for multiple scenarios
Eliminate overlap: avoid multiple tests covering the same functionality
Consolidate assertions using chained expectations (e.g., expect($x)->toBe(1)->and($y)->toBe(2))
Mock only external dependencies; do not perform real I/O in unit tests
Avoid performance tests unless performance is the primary concern
Forbidden in tests: toBeInstanceOf(...), toBeArray(), generic not->toBeNull(), expect(true)->toBeTrue(), and sleep(...) (use time mocking)
Prefer meaningful assertions on behavior and values (e.g., checking specific config values, validator outcomes, and configured mock interactions)
Unit tests: mock all external dependencies, test single units in isolation, complete in milliseconds
Integration tests: allow real file operations and external processes; cover CLI commands and full workflows
Do not run or gate tests on PHPStan compliance; ignore PHPStan issues in tests and avoid excessive phpdoc just for types

Files:

  • tests/Unit/ContainerTest.php
  • tests/TestHelpers.php
  • tests/Integration/Console/Server/ServerAddCommandTest.php
🧠 Learnings (7)
📚 Learning: 2025-10-04T14:36:00.065Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-10-04T14:36:00.065Z
Learning: Applies to tests/**/*.php : In tests, it's allowed to instantiate the Container directly for isolation

Applied to files:

  • tests/Unit/ContainerTest.php
  • tests/TestHelpers.php
  • .cursor/rules/02-tests.mdc
📚 Learning: 2025-10-04T14:36:28.928Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-04T14:36:28.928Z
Learning: Applies to tests/**/*.php : In unit tests, prefer manual instantiation with explicit mocks (e.g., `new ClassName(...)`) over using the DI container

Applied to files:

  • tests/Unit/ContainerTest.php
  • tests/TestHelpers.php
  • .cursor/rules/02-tests.mdc
📚 Learning: 2025-10-04T14:36:28.928Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-04T14:36:28.928Z
Learning: Applies to tests/**/*.php : DI container conventions for production do not apply to tests

Applied to files:

  • tests/Unit/ContainerTest.php
  • .cursor/rules/02-tests.mdc
📚 Learning: 2025-10-04T14:36:28.928Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-04T14:36:28.928Z
Learning: Applies to tests/**/*.php : Use the DI container only in integration tests when verifying multiple services together or DI configuration

Applied to files:

  • tests/TestHelpers.php
  • .cursor/rules/02-tests.mdc
📚 Learning: 2025-10-04T14:36:28.928Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-04T14:36:28.928Z
Learning: Applies to tests/**/*.php : Unit tests: mock all external dependencies, test single units in isolation, complete in milliseconds

Applied to files:

  • .cursor/rules/02-tests.mdc
📚 Learning: 2025-10-04T14:36:28.928Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-04T14:36:28.928Z
Learning: Layer strategy: CLI commands get integration tests; business services get unit tests with mocks; utilities/helpers get unit tests

Applied to files:

  • .cursor/rules/02-tests.mdc
📚 Learning: 2025-10-04T14:36:28.928Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-04T14:36:28.928Z
Learning: Applies to tests/**/*.php : Mock only external dependencies; do not perform real I/O in unit tests

Applied to files:

  • .cursor/rules/02-tests.mdc
🧬 Code graph analysis (3)
tests/Unit/ContainerTest.php (2)
tests/Fixtures/ContainerFixtures.php (6)
  • SimpleService (11-17)
  • ServiceWithMultipleDeps (43-58)
  • getSimple (49-52)
  • getComplex (54-57)
  • getDependency (37-40)
  • getDependency (165-168)
app/Container.php (2)
  • bind (46-50)
  • build (59-85)
tests/TestHelpers.php (5)
app/Services/PrompterService.php (1)
  • PrompterService (24-246)
app/Services/EnvService.php (1)
  • EnvService (12-134)
app/Services/InventoryService.php (1)
  • InventoryService (33-264)
app/Repositories/ServerRepository.php (1)
  • ServerRepository (15-168)
app/Container.php (2)
  • Container (23-250)
  • bind (46-50)
tests/Integration/Console/Server/ServerAddCommandTest.php (2)
tests/TestHelpers.php (1)
  • mockCommandContainer (373-401)
app/Container.php (1)
  • build (59-85)
🔇 Additional comments (7)
tests/Unit/ContainerTest.php (4)

135-146: LGTM!

The test correctly verifies basic binding functionality and fluent interface return.


148-159: LGTM!

Correctly validates that bound instances are used throughout the dependency resolution chain.


161-173: LGTM!

Properly verifies that binding overrides auto-wiring behavior.


175-188: LGTM!

Correctly verifies singleton behavior for bound instances.

tests/Integration/Console/Server/ServerAddCommandTest.php (1)

16-21: LGTM!

Helper correctly uses container-based command construction with service override support.

tests/TestHelpers.php (2)

11-11: LGTM!

Import addition is necessary for the mockCommandContainer function.


348-383: LGTM!

Function signature and documentation correctly describe the container-based testing approach.


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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
app/Container.php (1)

46-49: Validate bound instance types.

Please guard bind() with an instanceof $className check (throwing immediately) so accidental misbindings fail fast instead of surfacing later during constructor injection.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae59dec and c0bb864.

📒 Files selected for processing (10)
  • app/Container.php (2 hunks)
  • tests/Integration/Console/Server/ServerAddCommandTest.php (3 hunks)
  • tests/Integration/Console/Server/ServerDeleteCommandTest.php (1 hunks)
  • tests/Integration/Console/Server/ServerListCommandTest.php (1 hunks)
  • tests/TestHelpers.php (2 hunks)
  • tests/Unit/ContainerTest.php (1 hunks)
  • tests/Unit/TestHelpersTest.php (1 hunks)
  • tests/Unit/Traits/ConsoleInputTraitTest.php (1 hunks)
  • tests/Unit/Traits/ConsoleOutputTraitTest.php (1 hunks)
  • tests/Unit/Traits/ServerHelpersTraitTest.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.php

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

**/*.php: Eliminate single-use private methods by inlining them directly
Cache expensive computed values by initializing them in the constructor instead of recomputing
Prefer direct property access over method calls when appropriate to avoid call overhead
Organize code into comment-separated sections; prefer alphabetical ordering when it does not conflict with logical grouping

**/*.php: Adhere to PSR-12 for all PHP code
Declare strict_types and write code assuming strict typing
Prefer PHP 8.x language features where appropriate (union types, match, attributes, readonly)
Use import statements (use ...) instead of fully qualified class names in code
All methods must have explicit return types, and use proper generics in types (e.g., Collection<int, User>)
Use dependency injection instead of manually resolving or instantiating classes
Prefer Symfony component classes (e.g., Filesystem, Process) over native PHP functions for testability
All object creation must use $container->build(ClassName::class), except value objects, DTOs, and pure data structures
In production code, access the container via constructor injection (no service locator or global access)
Add DocBlock comments with minimalist descriptions, parameters, and return types for classes and functions
Use comments as visual separators with section headers/subheaders and avoid obvious or stale comments

Files:

  • tests/Integration/Console/Server/ServerListCommandTest.php
  • tests/Unit/Traits/ConsoleOutputTraitTest.php
  • tests/Unit/TestHelpersTest.php
  • tests/Unit/Traits/ServerHelpersTraitTest.php
  • tests/Integration/Console/Server/ServerAddCommandTest.php
  • tests/Unit/ContainerTest.php
  • tests/TestHelpers.php
  • tests/Integration/Console/Server/ServerDeleteCommandTest.php
  • tests/Unit/Traits/ConsoleInputTraitTest.php
  • app/Container.php
{tests/**,test/**,**/*@(Test|Spec).php}

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

Do not run or edit tests unless explicitly instructed

Files:

  • tests/Integration/Console/Server/ServerListCommandTest.php
  • tests/Unit/Traits/ConsoleOutputTraitTest.php
  • tests/Unit/TestHelpersTest.php
  • tests/Unit/Traits/ServerHelpersTraitTest.php
  • tests/Integration/Console/Server/ServerAddCommandTest.php
  • tests/Unit/ContainerTest.php
  • tests/TestHelpers.php
  • tests/Integration/Console/Server/ServerDeleteCommandTest.php
  • tests/Unit/Traits/ConsoleInputTraitTest.php
tests/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

tests/**/*.php: In tests, it's allowed to instantiate the Container directly for isolation
Exclude tests from PHPStan static analysis

tests/**/*.php: Write Pest tests using it() syntax
In unit tests, prefer manual instantiation with explicit mocks (e.g., new ClassName(...)) over using the DI container
Use the DI container only in integration tests when verifying multiple services together or DI configuration
DI container conventions for production do not apply to tests
Apply the AAA pattern (Arrange, Act, Assert) in every test
For exception tests, combine phases with a // ACT & ASSERT comment when the act triggers the assertion
Organize tests with describe() blocks and shared setup via beforeEach(); extract helpers/traits for DRY tests
Keep test files under 1.8x the size of the source they test, without sacrificing readability
Focus tests on core business logic; avoid testing the framework itself
Use dataset-driven testing with ->with([]) for multiple scenarios
Eliminate overlap: avoid multiple tests covering the same functionality
Consolidate assertions using chained expectations (e.g., expect($x)->toBe(1)->and($y)->toBe(2))
Mock only external dependencies; do not perform real I/O in unit tests
Avoid performance tests unless performance is the primary concern
Forbidden in tests: toBeInstanceOf(...), toBeArray(), generic not->toBeNull(), expect(true)->toBeTrue(), and sleep(...) (use time mocking)
Prefer meaningful assertions on behavior and values (e.g., checking specific config values, validator outcomes, and configured mock interactions)
Unit tests: mock all external dependencies, test single units in isolation, complete in milliseconds
Integration tests: allow real file operations and external processes; cover CLI commands and full workflows
Do not run or gate tests on PHPStan compliance; ignore PHPStan issues in tests and avoid excessive phpdoc just for types

Files:

  • tests/Integration/Console/Server/ServerListCommandTest.php
  • tests/Unit/Traits/ConsoleOutputTraitTest.php
  • tests/Unit/TestHelpersTest.php
  • tests/Unit/Traits/ServerHelpersTraitTest.php
  • tests/Integration/Console/Server/ServerAddCommandTest.php
  • tests/Unit/ContainerTest.php
  • tests/TestHelpers.php
  • tests/Integration/Console/Server/ServerDeleteCommandTest.php
  • tests/Unit/Traits/ConsoleInputTraitTest.php
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-04T14:36:28.928Z
Learning: Applies to tests/**/*.php : Use the DI container only in integration tests when verifying multiple services together or DI configuration
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-04T14:36:28.928Z
Learning: Applies to tests/**/*.php : In unit tests, prefer manual instantiation with explicit mocks (e.g., `new ClassName(...)`) over using the DI container
📚 Learning: 2025-10-04T14:36:28.928Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-04T14:36:28.928Z
Learning: Applies to tests/**/*.php : In unit tests, prefer manual instantiation with explicit mocks (e.g., `new ClassName(...)`) over using the DI container

Applied to files:

  • tests/Unit/Traits/ConsoleOutputTraitTest.php
📚 Learning: 2025-10-04T14:36:00.065Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-10-04T14:36:00.065Z
Learning: Applies to tests/**/*.php : In tests, it's allowed to instantiate the Container directly for isolation

Applied to files:

  • tests/Unit/ContainerTest.php
🧬 Code graph analysis (9)
tests/Integration/Console/Server/ServerListCommandTest.php (2)
tests/TestHelpers.php (1)
  • mockCommandContainer (373-401)
app/Container.php (1)
  • build (59-85)
tests/Unit/Traits/ConsoleOutputTraitTest.php (3)
tests/TestHelpers.php (1)
  • mockCommandContainer (373-401)
app/Container.php (1)
  • build (59-85)
tests/Fixtures/TestConsoleCommand.php (1)
  • TestConsoleCommand (25-236)
tests/Unit/TestHelpersTest.php (3)
tests/TestHelpers.php (2)
  • mockCommandContainer (373-401)
  • mockSSHServiceWithBehavior (228-231)
app/Container.php (1)
  • build (59-85)
app/Repositories/ServerRepository.php (2)
  • ServerRepository (15-168)
  • all (83-93)
tests/Unit/Traits/ServerHelpersTraitTest.php (3)
tests/TestHelpers.php (1)
  • mockCommandContainer (373-401)
app/Container.php (1)
  • build (59-85)
tests/Fixtures/TestConsoleCommand.php (1)
  • TestConsoleCommand (25-236)
tests/Integration/Console/Server/ServerAddCommandTest.php (3)
tests/TestHelpers.php (1)
  • mockCommandContainer (373-401)
app/Container.php (1)
  • build (59-85)
app/Repositories/ServerRepository.php (1)
  • ServerRepository (15-168)
tests/Unit/ContainerTest.php (2)
tests/Fixtures/ContainerFixtures.php (6)
  • SimpleService (11-17)
  • ServiceWithMultipleDeps (43-58)
  • getSimple (49-52)
  • getComplex (54-57)
  • getDependency (37-40)
  • getDependency (165-168)
app/Container.php (2)
  • bind (46-50)
  • build (59-85)
tests/TestHelpers.php (6)
app/Services/PrompterService.php (1)
  • PrompterService (24-246)
app/Services/EnvService.php (1)
  • EnvService (12-134)
app/Services/InventoryService.php (1)
  • InventoryService (33-264)
app/Repositories/ServerRepository.php (1)
  • ServerRepository (15-168)
app/Services/SSHService.php (1)
  • SSHService (40-327)
app/Container.php (2)
  • Container (23-250)
  • bind (46-50)
tests/Integration/Console/Server/ServerDeleteCommandTest.php (2)
tests/TestHelpers.php (1)
  • mockCommandContainer (373-401)
app/Container.php (1)
  • build (59-85)
tests/Unit/Traits/ConsoleInputTraitTest.php (3)
tests/TestHelpers.php (1)
  • mockCommandContainer (373-401)
app/Container.php (1)
  • build (59-85)
tests/Fixtures/TestConsoleCommand.php (1)
  • TestConsoleCommand (25-236)
🔇 Additional comments (7)
tests/Integration/Console/Server/ServerListCommandTest.php (1)

30-31: LGTM! Container-based assembly aligns with integration test guidelines.

The refactor correctly uses mockCommandContainer to construct the command via the container, simplifying dependency management in integration tests.

Based on learnings: Integration tests are the appropriate place for container usage when verifying multiple services together.

tests/Integration/Console/Server/ServerDeleteCommandTest.php (1)

30-31: LGTM! Consistent container-based pattern.

The refactor matches the approach in other integration tests, maintaining consistency across the test suite.

tests/Integration/Console/Server/ServerAddCommandTest.php (2)

19-20: LGTM! Simplified test setup.

Container-based construction eliminates manual dependency wiring, making the test helper cleaner and more maintainable.


289-290: LGTM! Proper integration test pattern for persistence verification.

Building the command and then resolving the repository from the same container correctly verifies that the command's operations persisted data through the shared container dependencies.

Also applies to: 307-307

tests/Unit/TestHelpersTest.php (1)

172-210: LGTM! Unit tests appropriately validate container behavior.

These tests verify that mockCommandContainer correctly binds services and supports overrides. Testing the container's binding mechanism is an appropriate use of the container in unit tests, as it validates test infrastructure rather than business logic.

tests/TestHelpers.php (2)

11-11: LGTM! Import added for PrompterService.

The new import supports the expanded parameter list in mockCommandContainer.


348-401: LGTM! Well-designed container-based test helper.

The refactor from mockTestConsoleCommand to mockCommandContainer provides:

  • Flexible service override capability via optional parameters
  • Clean binding of mock services to the container
  • Support for inventory data injection
  • Clear documentation with practical examples

This enables integration tests to use container-based assembly while maintaining testability through service overrides.

Comment on lines +13 to 16
$container = mockCommandContainer();
$this->command = $container->build(\Bigpixelrocket\DeployerPHP\Tests\Fixtures\TestConsoleCommand::class);
$this->tester = new CommandTester($this->command);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Container-based setup violates unit-test rule.

Please stop using mockCommandContainer()->build(...) in this unit spec; construct the command explicitly so the test remains a true unit test per our standards. Based on learnings

🤖 Prompt for AI Agents
In tests/Unit/Traits/ConsoleInputTraitTest.php around lines 13 to 16, the test
currently uses mockCommandContainer()->build(...) which relies on the container
and violates the unit-test rule; replace the container-based construction by
instantiating the TestConsoleCommand directly and supplying any dependencies as
explicit mocks or stubs. Remove the mockCommandContainer() call, create
necessary mock dependencies (or use real lightweight/fake implementations) and
pass them into new
\Bigpixelrocket\DeployerPHP\Tests\Fixtures\TestConsoleCommand(...), then create
the CommandTester with that instance so the test remains a true unit test.

Comment on lines +13 to 16
$container = mockCommandContainer();
$this->command = $container->build(\Bigpixelrocket\DeployerPHP\Tests\Fixtures\TestConsoleCommand::class);
$this->tester = new CommandTester($this->command);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unit test should avoid the container.

These trait tests now resolve the command through the DI container, which conflicts with our unit-testing rule to instantiate subjects manually with explicit mocks. Please revert to direct construction (e.g., new TestConsoleCommand(...)) and keep container usage for integration coverage. Based on learnings

🤖 Prompt for AI Agents
In tests/Unit/Traits/ConsoleOutputTraitTest.php around lines 13 to 16, the test
currently resolves the command via the DI container
(mockCommandContainer()->build(...)); change this to construct the command
directly (new TestConsoleCommand(...)) and pass explicit mock dependencies
required by the command (create mocks/stubs for each constructor parameter) so
the test remains a pure unit test; then instantiate CommandTester with the newly
constructed command. Leave any container-based resolution to integration tests
only.

Comment on lines +14 to 16
$container = mockCommandContainer();
$this->command = $container->build(\Bigpixelrocket\DeployerPHP\Tests\Fixtures\TestConsoleCommand::class);
$this->tester = new CommandTester($this->command);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Inline subject creation for unit scope.

Same concern here: building the command via the container breaks the “manual wiring in unit tests” guideline. Instantiate TestConsoleCommand directly with the mocked services instead. Based on learnings

🤖 Prompt for AI Agents
In tests/Unit/Traits/ServerHelpersTraitTest.php around lines 14 to 16, the test
builds the command via the DI container which violates the unit test guideline
to manually wire dependencies; replace the container build with a direct
instantiation of \Bigpixelrocket\DeployerPHP\Tests\Fixtures\TestConsoleCommand
by creating or reusing the mocked services used in this test and passing them
into the command's constructor, then create the CommandTester with that instance
(remove the container usage and ensure all required dependencies are explicitly
mocked and injected).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
app/Container.php (1)

38-50: Consider broader use case in docblock.

The docblock states "Bind a concrete instance to a class name for testing," but this binding mechanism could be useful beyond testing (e.g., for runtime configuration or plugin systems). Consider updating the docblock to reflect a broader scope or clarifying that while designed primarily for testing, it's a general-purpose feature.

That said, the implementation is clean:

  • Fluent interface for chaining
  • Proper type safety with generics
  • Simple and focused
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae59dec and b51bed7.

📒 Files selected for processing (10)
  • app/Container.php (2 hunks)
  • tests/Integration/Console/Server/ServerAddCommandTest.php (3 hunks)
  • tests/Integration/Console/Server/ServerDeleteCommandTest.php (1 hunks)
  • tests/Integration/Console/Server/ServerListCommandTest.php (1 hunks)
  • tests/TestHelpers.php (2 hunks)
  • tests/Unit/ContainerTest.php (1 hunks)
  • tests/Unit/TestHelpersTest.php (1 hunks)
  • tests/Unit/Traits/ConsoleInputTraitTest.php (1 hunks)
  • tests/Unit/Traits/ConsoleOutputTraitTest.php (1 hunks)
  • tests/Unit/Traits/ServerHelpersTraitTest.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.php

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

**/*.php: Eliminate single-use private methods by inlining them directly
Cache expensive computed values by initializing them in the constructor instead of recomputing
Prefer direct property access over method calls when appropriate to avoid call overhead
Organize code into comment-separated sections; prefer alphabetical ordering when it does not conflict with logical grouping

**/*.php: Adhere to PSR-12 for all PHP code
Declare strict_types and write code assuming strict typing
Prefer PHP 8.x language features where appropriate (union types, match, attributes, readonly)
Use import statements (use ...) instead of fully qualified class names in code
All methods must have explicit return types, and use proper generics in types (e.g., Collection<int, User>)
Use dependency injection instead of manually resolving or instantiating classes
Prefer Symfony component classes (e.g., Filesystem, Process) over native PHP functions for testability
All object creation must use $container->build(ClassName::class), except value objects, DTOs, and pure data structures
In production code, access the container via constructor injection (no service locator or global access)
Add DocBlock comments with minimalist descriptions, parameters, and return types for classes and functions
Use comments as visual separators with section headers/subheaders and avoid obvious or stale comments

Files:

  • tests/Unit/ContainerTest.php
  • tests/Unit/Traits/ServerHelpersTraitTest.php
  • tests/Unit/Traits/ConsoleInputTraitTest.php
  • tests/Unit/TestHelpersTest.php
  • app/Container.php
  • tests/Integration/Console/Server/ServerDeleteCommandTest.php
  • tests/Unit/Traits/ConsoleOutputTraitTest.php
  • tests/Integration/Console/Server/ServerAddCommandTest.php
  • tests/Integration/Console/Server/ServerListCommandTest.php
  • tests/TestHelpers.php
{tests/**,test/**,**/*@(Test|Spec).php}

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

Do not run or edit tests unless explicitly instructed

Files:

  • tests/Unit/ContainerTest.php
  • tests/Unit/Traits/ServerHelpersTraitTest.php
  • tests/Unit/Traits/ConsoleInputTraitTest.php
  • tests/Unit/TestHelpersTest.php
  • tests/Integration/Console/Server/ServerDeleteCommandTest.php
  • tests/Unit/Traits/ConsoleOutputTraitTest.php
  • tests/Integration/Console/Server/ServerAddCommandTest.php
  • tests/Integration/Console/Server/ServerListCommandTest.php
  • tests/TestHelpers.php
tests/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

tests/**/*.php: In tests, it's allowed to instantiate the Container directly for isolation
Exclude tests from PHPStan static analysis

tests/**/*.php: Write Pest tests using it() syntax
In unit tests, prefer manual instantiation with explicit mocks (e.g., new ClassName(...)) over using the DI container
Use the DI container only in integration tests when verifying multiple services together or DI configuration
DI container conventions for production do not apply to tests
Apply the AAA pattern (Arrange, Act, Assert) in every test
For exception tests, combine phases with a // ACT & ASSERT comment when the act triggers the assertion
Organize tests with describe() blocks and shared setup via beforeEach(); extract helpers/traits for DRY tests
Keep test files under 1.8x the size of the source they test, without sacrificing readability
Focus tests on core business logic; avoid testing the framework itself
Use dataset-driven testing with ->with([]) for multiple scenarios
Eliminate overlap: avoid multiple tests covering the same functionality
Consolidate assertions using chained expectations (e.g., expect($x)->toBe(1)->and($y)->toBe(2))
Mock only external dependencies; do not perform real I/O in unit tests
Avoid performance tests unless performance is the primary concern
Forbidden in tests: toBeInstanceOf(...), toBeArray(), generic not->toBeNull(), expect(true)->toBeTrue(), and sleep(...) (use time mocking)
Prefer meaningful assertions on behavior and values (e.g., checking specific config values, validator outcomes, and configured mock interactions)
Unit tests: mock all external dependencies, test single units in isolation, complete in milliseconds
Integration tests: allow real file operations and external processes; cover CLI commands and full workflows
Do not run or gate tests on PHPStan compliance; ignore PHPStan issues in tests and avoid excessive phpdoc just for types

Files:

  • tests/Unit/ContainerTest.php
  • tests/Unit/Traits/ServerHelpersTraitTest.php
  • tests/Unit/Traits/ConsoleInputTraitTest.php
  • tests/Unit/TestHelpersTest.php
  • tests/Integration/Console/Server/ServerDeleteCommandTest.php
  • tests/Unit/Traits/ConsoleOutputTraitTest.php
  • tests/Integration/Console/Server/ServerAddCommandTest.php
  • tests/Integration/Console/Server/ServerListCommandTest.php
  • tests/TestHelpers.php
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-04T14:36:28.928Z
Learning: Applies to tests/**/*.php : Use the DI container only in integration tests when verifying multiple services together or DI configuration
📚 Learning: 2025-10-04T14:36:00.065Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-10-04T14:36:00.065Z
Learning: Applies to tests/**/*.php : In tests, it's allowed to instantiate the Container directly for isolation

Applied to files:

  • tests/Unit/ContainerTest.php
📚 Learning: 2025-10-04T14:36:28.928Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-04T14:36:28.928Z
Learning: Applies to tests/**/*.php : In unit tests, prefer manual instantiation with explicit mocks (e.g., `new ClassName(...)`) over using the DI container

Applied to files:

  • tests/Unit/ContainerTest.php
📚 Learning: 2025-10-04T14:36:28.928Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-04T14:36:28.928Z
Learning: Applies to tests/**/*.php : DI container conventions for production do not apply to tests

Applied to files:

  • tests/Unit/ContainerTest.php
📚 Learning: 2025-10-04T14:36:28.928Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-04T14:36:28.928Z
Learning: Applies to tests/**/*.php : Use the DI container only in integration tests when verifying multiple services together or DI configuration

Applied to files:

  • tests/Unit/ContainerTest.php
🧬 Code graph analysis (9)
tests/Unit/ContainerTest.php (2)
tests/Fixtures/ContainerFixtures.php (6)
  • SimpleService (11-17)
  • ServiceWithMultipleDeps (43-58)
  • getSimple (49-52)
  • getComplex (54-57)
  • getDependency (37-40)
  • getDependency (165-168)
app/Container.php (2)
  • bind (46-50)
  • build (59-85)
tests/Unit/Traits/ServerHelpersTraitTest.php (3)
tests/TestHelpers.php (1)
  • mockCommandContainer (373-401)
app/Container.php (1)
  • build (59-85)
tests/Fixtures/TestConsoleCommand.php (1)
  • TestConsoleCommand (25-236)
tests/Unit/Traits/ConsoleInputTraitTest.php (3)
tests/TestHelpers.php (1)
  • mockCommandContainer (373-401)
app/Container.php (1)
  • build (59-85)
tests/Fixtures/TestConsoleCommand.php (1)
  • TestConsoleCommand (25-236)
tests/Unit/TestHelpersTest.php (4)
tests/TestHelpers.php (2)
  • mockCommandContainer (373-401)
  • mockSSHServiceWithBehavior (228-231)
app/Container.php (1)
  • build (59-85)
tests/Fixtures/TestConsoleCommand.php (1)
  • TestConsoleCommand (25-236)
app/Repositories/ServerRepository.php (2)
  • ServerRepository (15-168)
  • all (83-93)
tests/Integration/Console/Server/ServerDeleteCommandTest.php (2)
tests/TestHelpers.php (1)
  • mockCommandContainer (373-401)
app/Container.php (1)
  • build (59-85)
tests/Unit/Traits/ConsoleOutputTraitTest.php (3)
tests/TestHelpers.php (1)
  • mockCommandContainer (373-401)
app/Container.php (1)
  • build (59-85)
tests/Fixtures/TestConsoleCommand.php (1)
  • TestConsoleCommand (25-236)
tests/Integration/Console/Server/ServerAddCommandTest.php (3)
tests/TestHelpers.php (1)
  • mockCommandContainer (373-401)
app/Container.php (1)
  • build (59-85)
app/Repositories/ServerRepository.php (1)
  • ServerRepository (15-168)
tests/Integration/Console/Server/ServerListCommandTest.php (2)
tests/TestHelpers.php (1)
  • mockCommandContainer (373-401)
app/Container.php (1)
  • build (59-85)
tests/TestHelpers.php (3)
app/Services/PrompterService.php (1)
  • PrompterService (24-246)
app/Repositories/ServerRepository.php (1)
  • ServerRepository (15-168)
app/Container.php (2)
  • Container (23-250)
  • bind (46-50)
🪛 GitHub Actions: Rector
tests/TestHelpers.php

[error] 384-384: Rector dry-run indicated 1 file would be changed. Applied rules: NullCoalescingOperatorRector. Exit code 2 from 'vendor/bin/rector --dry-run'.

🔇 Additional comments (9)
tests/Integration/Console/Server/ServerListCommandTest.php (1)

30-31: LGTM!

Container-based command construction in integration tests aligns with the coding guidelines, which state that the DI container should be used "when verifying multiple services together or DI configuration."

Based on learnings.

tests/Unit/ContainerTest.php (1)

135-173: LGTM!

The new tests thoroughly verify the bind() functionality:

  • Fluent interface returns the container
  • Bound instances are returned on subsequent builds
  • Bound instances propagate through dependency resolution
  • Bindings correctly override auto-wiring

The tests follow the AAA pattern and provide good coverage of the new feature.

tests/Unit/TestHelpersTest.php (1)

172-209: LGTM!

The new tests thoroughly verify mockCommandContainer functionality:

  • Container correctly binds all BaseCommand dependencies
  • Service overrides work as expected
  • Inventory data overrides propagate through the repository

The tests follow the AAA pattern and provide good coverage for the new helper.

app/Container.php (1)

61-65: LGTM!

The binding check in build() is correctly placed at the beginning to short-circuit auto-wiring when a binding exists. The type annotation ensures type safety is preserved.

tests/Integration/Console/Server/ServerDeleteCommandTest.php (1)

30-31: LGTM!

Container-based command construction in integration tests aligns with the coding guidelines, which state that the DI container should be used "when verifying multiple services together or DI configuration."

Based on learnings.

tests/Integration/Console/Server/ServerAddCommandTest.php (2)

19-21: Good switch to container-built command in integration tests

Building the command via the container is appropriate here and aligns with the new bind/build mechanism.


289-291: Persistence check correctly reuses the bound repository

Fetching ServerRepository from the same container ensures you validate real persistence on the exact instance the command used. Solid integration assertion.

Also applies to: 307-307

tests/TestHelpers.php (2)

11-11: Import looks correct

PrompterService import is needed for the new binding.


348-401: Fix CI: install dev dependencies & apply Rector fixes. Ensure vendor/bin/rector is available by running composer install --dev, then run vendor/bin/rector --dry-run and vendor/bin/rector; commit the resulting diff to unblock CI.

Comment thread tests/TestHelpers.php
Comment on lines +348 to 401
if (!function_exists('mockCommandContainer')) {
/**
* Create a TestConsoleCommand for testing with mocked dependencies.
* Create a Container with mocked dependencies for command testing.
*
* Returns a fully configured command with all dependencies injected.
* Useful for testing BaseCommand, console traits, and server helpers.
* Returns a Container with sensible mock defaults for all BaseCommand dependencies.
* Override specific services by passing them as arguments.
*
* @example
* // Default configuration
* $command = mockTestConsoleCommand();
* // Build command with default mocks
* $container = mockCommandContainer();
* $command = $container->build(ServerListCommand::class);
*
* @example
* // Override SSH service for connection testing
* $ssh = mockSSHServiceWithBehavior(canConnect: false);
* $container = mockCommandContainer(ssh: $ssh);
* $command = $container->build(ServerAddCommand::class);
*
* @example
* // Custom environment and inventory
* $command = mockTestConsoleCommand(
* envFileExists: true,
* envContent: 'API_KEY=secret',
* inventoryFileExists: true,
* inventoryData: ['servers' => []]
* // Override inventory data for pre-populated servers
* $container = mockCommandContainer(
* inventoryData: ['servers' => ['web1' => ['host' => '192.168.1.1']]]
* );
* $command = $container->build(ServerListCommand::class);
*/
function mockTestConsoleCommand(
function mockCommandContainer(
?EnvService $env = null,
?InventoryService $inventory = null,
?ServerRepository $servers = null,
?SSHService $ssh = null,
?PrompterService $prompter = null,
bool $envFileExists = true,
string $envContent = 'API_KEY=test_value',
bool $inventoryFileExists = true,
array|string $inventoryData = []
): TestConsoleCommand {
): Container {
$container = new Container();
$env = mockEnvService($envFileExists, $envContent);
$inventory = mockInventoryService($inventoryFileExists, $inventoryData);
$servers = mockServerRepository($inventoryFileExists, $inventoryData);
$ssh = mockSSHService();
$prompter = mockPrompter();

return new TestConsoleCommand($container, $env, $inventory, $servers, $ssh, $prompter);
// Build or use provided services
$env = $env ?? mockEnvService($envFileExists, $envContent);
$inventory = $inventory ?? mockInventoryService($inventoryFileExists, $inventoryData);
$servers = $servers ?? mockServerRepository($inventoryFileExists, $inventoryData);
$ssh = $ssh ?? mockSSHService();
$prompter = $prompter ?? mockPrompter();

// Bind services to container
$container->bind(EnvService::class, $env);
$container->bind(InventoryService::class, $inventory);
$container->bind(ServerRepository::class, $servers);
$container->bind(SSHService::class, $ssh);
$container->bind(PrompterService::class, $prompter);

return $container;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Limit container helper usage to integration tests

Avoid using mockCommandContainer in unit tests. Unit tests should manually instantiate the SUT with explicit mocks; reserve the container for integration tests that verify DI wiring. Based on learnings

Run to spot accidental usage in unit tests:


🏁 Script executed:

#!/bin/bash
# Find any unit tests using the container-based helper
rg -n -C2 -g 'tests/Unit/**' -P '\bmockCommandContainer\s*\('

Length of output: 2788


Avoid mockCommandContainer in unit tests

Unit tests under tests/Unit/** (e.g., tests/Unit/TestHelpersTest.php and tests/Unit/Traits/*.php) call mockCommandContainer(). Replace these with direct instantiation of the SUT and explicit mocks; reserve mockCommandContainer for integration tests that verify DI wiring.

🧰 Tools
🪛 GitHub Actions: Rector

[error] 384-384: Rector dry-run indicated 1 file would be changed. Applied rules: NullCoalescingOperatorRector. Exit code 2 from 'vendor/bin/rector --dry-run'.

Comment thread tests/TestHelpers.php
Comment on lines +386 to +392
// Build or use provided services
$env = $env ?? mockEnvService($envFileExists, $envContent);
$inventory = $inventory ?? mockInventoryService($inventoryFileExists, $inventoryData);
$servers = $servers ?? mockServerRepository($inventoryFileExists, $inventoryData);
$ssh = $ssh ?? mockSSHService();
$prompter = $prompter ?? mockPrompter();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Bind ServerRepository to the same InventoryService instance

Currently, InventoryService is created twice: once for the container binding and once inside mockServerRepository(). This can desynchronize repo state from any code resolving InventoryService from the container. Initialize the repository with the already-created $inventory to keep state consistent.

Apply this diff:

-        $env = $env ?? mockEnvService($envFileExists, $envContent);
-        $inventory = $inventory ?? mockInventoryService($inventoryFileExists, $inventoryData);
-        $servers = $servers ?? mockServerRepository($inventoryFileExists, $inventoryData);
-        $ssh = $ssh ?? mockSSHService();
-        $prompter = $prompter ?? mockPrompter();
+        $env = $env ?? mockEnvService($envFileExists, $envContent);
+        $inventory = $inventory ?? mockInventoryService($inventoryFileExists, $inventoryData);
+        if ($servers === null) {
+            $servers = new ServerRepository();
+            $servers->loadInventory($inventory);
+        }
+        $ssh = $ssh ?? mockSSHService();
+        $prompter = $prompter ?? mockPrompter();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Build or use provided services
$env = $env ?? mockEnvService($envFileExists, $envContent);
$inventory = $inventory ?? mockInventoryService($inventoryFileExists, $inventoryData);
$servers = $servers ?? mockServerRepository($inventoryFileExists, $inventoryData);
$ssh = $ssh ?? mockSSHService();
$prompter = $prompter ?? mockPrompter();
// Build or use provided services
$env = $env ?? mockEnvService($envFileExists, $envContent);
$inventory = $inventory ?? mockInventoryService($inventoryFileExists, $inventoryData);
if ($servers === null) {
$servers = new ServerRepository();
$servers->loadInventory($inventory);
}
$ssh = $ssh ?? mockSSHService();
$prompter = $prompter ?? mockPrompter();
🤖 Prompt for AI Agents
In tests/TestHelpers.php around lines 386 to 392, the InventoryService is being
created twice which can desynchronize state: replace the call that creates a new
InventoryService inside mockServerRepository with the already-initialized
$inventory instance so the repository shares the same InventoryService; update
the $servers assignment to use $inventory (e.g. $servers = $servers ??
mockServerRepository($inventory)) and ensure mockServerRepository accepts an
InventoryService parameter and the container binding uses that same $inventory.

Comment on lines +13 to +14
$container = mockCommandContainer();
$this->command = $container->build(\Bigpixelrocket\DeployerPHP\Tests\Fixtures\TestConsoleCommand::class);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reconsider container usage in unit tests.

This unit test now uses the DI container to construct the test command, which conflicts with the coding guidelines. According to the guidelines:

"In unit tests, prefer manual instantiation with explicit mocks (e.g., new ClassName(...)) over using the DI container"

"Use the DI container only in integration tests when verifying multiple services together or DI configuration"

For unit tests that focus on a single trait's behavior, consider reverting to direct instantiation with explicit mocks. The container approach is more appropriate for integration tests.

Based on learnings.

🤖 Prompt for AI Agents
In tests/Unit/Traits/ConsoleInputTraitTest.php around lines 13-14, the test is
constructing the command via the DI container which violates unit-test guidance;
replace the container usage with direct instantiation of TestConsoleCommand and
pass explicit PHPUnit mocks for any constructor dependencies (use
$this->createMock(...) for each dependency the command requires), remove
mockCommandContainer() from this unit test, and adjust setUp/teardown so the
command and its mocked dependencies are created and injected directly in the
test file.

Comment on lines +13 to +14
$container = mockCommandContainer();
$this->command = $container->build(\Bigpixelrocket\DeployerPHP\Tests\Fixtures\TestConsoleCommand::class);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reconsider container usage in unit tests.

This unit test now uses the DI container to construct the test command, which conflicts with the coding guidelines. According to the guidelines:

"In unit tests, prefer manual instantiation with explicit mocks (e.g., new ClassName(...)) over using the DI container"

"Use the DI container only in integration tests when verifying multiple services together or DI configuration"

For unit tests that focus on a single trait's behavior, consider reverting to direct instantiation with explicit mocks as was done previously. The container approach is more appropriate for integration tests.

Based on learnings.

🤖 Prompt for AI Agents
In tests/Unit/Traits/ConsoleOutputTraitTest.php around lines 13-14, the test is
constructing the command via the DI container which violates unit test
guidelines; replace container construction with direct instantiation and
explicit mocks: create any required dependency doubles (mocks/stubs) for the
command's constructor arguments and instantiate the TestConsoleCommand with new
using those mocks so the test isolates the trait behavior without relying on the
container.

Comment on lines +14 to +15
$container = mockCommandContainer();
$this->command = $container->build(\Bigpixelrocket\DeployerPHP\Tests\Fixtures\TestConsoleCommand::class);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reconsider container usage in unit tests.

This unit test now uses the DI container to construct the test command, which conflicts with the coding guidelines. According to the guidelines:

"In unit tests, prefer manual instantiation with explicit mocks (e.g., new ClassName(...)) over using the DI container"

"Use the DI container only in integration tests when verifying multiple services together or DI configuration"

For unit tests that focus on a single trait's behavior, consider reverting to direct instantiation with explicit mocks. The container approach is more appropriate for integration tests.

Based on learnings.

🤖 Prompt for AI Agents
In tests/Unit/Traits/ServerHelpersTraitTest.php around lines 14-15, the test
uses the DI container to build the TestConsoleCommand which violates unit-test
guidelines; replace the container usage with direct instantiation (new
TestConsoleCommand(...)) and supply explicit mocks/stubs for any constructor
dependencies the command needs (use PHPUnit mocks for services, input/output, or
other collaborators), update any setup to inject those mocks, and remove the
container helper so the test focuses on the trait in isolation.

Update architecture and testing rules to document the new
mockCommandContainer() pattern and Container::bind() method.

Testing Rules:
- Add mockCommandContainer() examples for command tests
- Document service override and inventory data patterns
- Add maintenance note for updating mockCommandContainer()
- Clarify when to use manual instantiation vs container

Architecture Rules:
- Document Container::bind() for test mocking
- Show how bound instances override auto-wiring
- Maintain clear separation between production and test usage

This ensures the new pattern is properly documented and
becomes the standard approach for command testing.
…tency

- Fix architecture violation in ServerAddCommandTest persistence test
- Use consistent helper pattern across all server command tests
- Reorder mockCommandContainer parameters (most common first: ssh, prompter)
- Add singleton behavior test for Container bind() method
- Simplify persistence test assertions (verify via output)

All tests pass (267 tests, 583 assertions)
@loadinglucian
loadinglucian merged commit 1f61497 into main Oct 10, 2025
4 of 5 checks passed
@loadinglucian
loadinglucian deleted the refactor/tests-container-based-mocking branch October 10, 2025 11:08
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.

1 participant