Files
playbook/antigravity-awesome-skills/skills/test-guard/references/phpunit.md
T
2026-07-18 00:02:59 +00:00

57 lines
2.9 KiB
Markdown

# Test Guard — PHP / PHPUnit / Pest Patterns
Concrete applications of the nine rules for PHP projects, including WordPress and WooCommerce. Read this when reviewing or writing PHP tests.
## Rule 2: Mock boundaries in PHP
Justified mock targets:
- HTTP: Guzzle handlers/middleware, `pre_http_request` filter in WordPress
- External SDKs: payment gateways, mail providers, LLM API clients
- Clock: inject a clock (`psr/clock`) instead of calling `time()` directly
- Filesystem on external paths (prefer `vfsStream` or temp dirs over mocking)
Unjustified mocks:
- Mockery/Prophecy doubles for the project's own value objects, DTOs, or entities — construct real instances (Rule 8)
- Mocking internal services just to isolate a class — if wiring is painful, fix the constructor, don't fake the collaborator
- Partial mocks of the class under test — you are no longer testing the class
## Rule 3: Data providers
```php
/**
* @see Rule 3 — variants of one scenario belong in a data provider.
*/
#[DataProvider('provideSlugCases')]
public function test_slugify_normalizes_input( string $raw, string $expected ): void {
$this->assertSame( $expected, slugify( $raw ) );
}
public static function provideSlugCases(): array {
return array(
'lowercases' => array( 'Hello World', 'hello-world' ),
'strips padding' => array( ' padded ', 'padded' ),
'transliterates' => array( 'Café Menu', 'cafe-menu' ),
);
}
```
Pest equivalent: `it('normalizes slug', ...)->with([...])`.
## WordPress-specific boundaries
- **Integration tests** (`WP_UnitTestCase` / `wp-env` / `wp-cli scaffold`): use the real WordPress test framework with factories — `self::factory()->post->create()`, `self::factory()->user->create()`. Don't mock `WP_Post` or `WP_User`; the factories exist precisely so you don't have to (Rule 8).
- **Unit tests without WordPress loaded** (Brain Monkey / WP_Mock): mocking WordPress functions like `get_option()` or `apply_filters()` is a boundary mock and justified. But assert what *your code does* with the values, not that `get_option` was called with specific args (Rule 1).
- Mock outbound HTTP with the `pre_http_request` filter rather than patching `wp_remote_get` internals.
- Don't test that WordPress sanitizes, escapes, or that hooks fire — that's core's guarantee (Rule 7). Test your callback's behavior given an input.
## WooCommerce notes
- Build real `WC_Product` / `WC_Order` objects via `WC_Helper_Product` and `WC_Helper_Order` in integration tests — never `MagicMock`-style doubles of them (Rule 8).
- Cart and checkout logic is stateful: prefer integration tests over heavily mocked unit tests; mocked carts hide hook-ordering bugs.
## Rule 9: Real database
`WP_UnitTestCase` already wraps each test in a transaction against a real schema — use it for query, meta, and persistence logic instead of mocking `$wpdb`. Mocking `$wpdb->prepare` or `$wpdb->get_results` to test a query builder tests nothing.