Plugin Testing¶
The plugin ships 89 PHPUnit tests in the default suite — 53 unit and 36
integration — plus 2 real-HTTP scraper tests in a separate scraper suite that
is excluded from CI. The boundary between the two main tiers is the controller
seam: anything that exercises Restart_Registry_Controller against fakes is
integration; anything else is unit.
Layout¶
plugin/tests/
├── bootstrap.php
├── assets/
│ └── TestItemUrls.txt # Static retailer URLs for the scraper regression set
├── Fakes/ # Test doubles (LambdaClientFake, etc.)
├── unit/
│ ├── ActivatorCreatePagesTest.php
│ ├── AffiliateConverterTest.php
│ ├── ControllerTruncateNameTest.php
│ └── RetailerApiTest.php
└── integration/
├── Controller/
│ ├── AccessControlTest.php
│ ├── MarkItemPurchasedTest.php
│ └── InviteTest.php
├── LambdaClient/
│ ├── LambdaClientTest.php
│ └── wp-clean.sql
└── Scraper/
└── ProductScraperTest.php # Real-HTTP — excluded from default suite
Running the suite¶
cd plugin
composer install
./vendor/bin/phpunit # default suite (unit + integration, no real HTTP)
./vendor/bin/phpunit --testsuite=unit
./vendor/bin/phpunit --testsuite=integration
./vendor/bin/phpunit --testsuite=scraper # real HTTP — needs internet access
./vendor/bin/phpunit --filter MarkItemPurchased # by class
The Make targets are equivalent:
make plugin-test # PHP + JS
make plugin-test-php # only PHP
make plugin-test-js # only Jest (admin UI)
make plugin-test-scraper # real-HTTP scraper suite (makes live requests to retailers)
Scraper tests make real HTTP requests
plugin-test-scraper (and --testsuite=scraper) hits live retailer pages. Run
it to validate scraping logic after changes to class-product-scraper.php, not
as part of the standard CI loop. Bot-detection failures cause individual tests to
be skipped, not fatal failures.
Mocking strategy¶
Brain\Monkey for WordPress functions¶
Unit tests stub global WP functions (get_post, update_post_meta,
is_email, wp_mail, etc.) with Brain\Monkey. Each test
case extends a base that calls Brain\\Monkey\\setUp() /
Brain\\Monkey\\tearDown() around each test.
Patchwork is loaded via patchwork.json to allow redefining the fixed
functions used by the plugin.
LambdaClientFake for the Lambda¶
Integration tests use a fake that satisfies the same interface as
Restart_Registry_Lambda_Client (get_item, get_items, create_item,
update_item, delete_item) backed by an in-memory PHP array. The fake is
injected via the controller constructor:
$lambda = new LambdaClientFake();
$controller = new Restart_Registry_Controller($lambda);
This is also why LambdaClient::request() is private rather than
protected — the boundary is the client class, not the HTTP method.
What is covered¶
| Area | File |
|---|---|
| Affiliate URL conversion (per retailer + edge cases) | unit/AffiliateConverterTest.php |
| Item-name truncation logic | unit/ControllerTruncateNameTest.php |
| Etsy retailer API metadata fetching | unit/RetailerApiTest.php |
| Activation page creation (ensure_page idempotency, template assignment, copy backfill) | unit/ActivatorCreatePagesTest.php |
can_view_registry / can_edit_registry matrix |
integration/Controller/AccessControlTest.php |
mark_item_purchased happy path + quantity exceeded + opt-out |
integration/Controller/MarkItemPurchasedTest.php |
send_invite + dedupe + email branching |
integration/Controller/InviteTest.php |
Lambda_Client HTTP behavior (auth headers, error mapping) |
integration/LambdaClient/LambdaClientTest.php |
| Product scraper — static URL regression set + live discovery (real HTTP) | integration/Scraper/ProductScraperTest.php |
JavaScript tests¶
The plugin's admin JS (settings page) is covered by Jest under
plugin/tests/js/. Configured in plugin/jest.config.js. Run with:
cd plugin
npm test
Writing a new test¶
- Pick the boundary. Pure-function or single-class?
tests/unit/. Crosses the controller?tests/integration/Controller/. Tests HTTP wire shape?tests/integration/LambdaClient/. - Stub WP functions you use. Brain\Monkey's
Functions\\when('foo')->...keeps the test scoped to the behavior under test. - Use the existing fakes.
LambdaClientFakeshould cover almost every integration scenario without reaching forMockery. - Name your test method
test_<does_what>and keep it ≤30 lines. Larger tests usually want to be split.
CI¶
make plugin-test runs in .github/workflows/ci.yml on every push. The plugin
release workflow (release-plugin.yml) runs the suite again before building
the distribution zip.