name: rmfa-testing description: Guides writing and running tests for the Restrict Media File Access WordPress plugin using Codeception/wp-browser integration tests and Codeception E2E tests. Use when writing tests, debugging test failures, or adding coverage for new features.
Skill: RMFA Testing
When to Use
Use this skill when:
- Writing or modifying tests for the RMFA plugin.
- Running the test suite (integration or E2E Codeception).
- Debugging test failures.
- Adding test coverage for new features.
Test Frameworks
| Framework | Location | Purpose |
|---|---|---|
| Codeception + wp-browser | tests/Integration/ |
WordPress integration tests (DB, hooks, filters). |
| Codeception + wp-browser | tests/EndToEnd/ |
Codeception browser E2E tests. |
Dependencies
- PHP:
lucatume/wp-browser^4. - JS:
@wordpress/env^10.8. - Infrastructure: Docker (for wp-env), Selenium with Chromium (for E2E).
Running Tests
All commands run from the restrict-media-file-access/ plugin root:
# Start wp-env (required for all tests)
npm run wp-env:start
# Integration tests
composer tests:run:integration
# E2E tests (Codeception + Selenium)
composer tests:run:end-to-end
# All Codeception tests (integration + E2E)
npm run tests:run
# Via wp-env (runs inside container)
npm run tests:run:integration
npm run tests:run:end-to-end
# Clean Codeception output
composer tests:clean
Local Setup (macOS)
- Install Docker, Composer, Node.js.
- Enable host networking in Docker settings (
Resources > Network). - Start Selenium:
docker run -d --shm-size="2g" --net=host --name="selenium-chromium" selenium/standalone-chromium:latest - Install dependencies:
composer run-script packages-install && npm install - Copy
tests/.dist.envtotests/.env. - Create database fixture:
npm run tests:export-db - Run tests:
npm run tests:run
Test Configuration
Codeception Config (codeception.dist.yml)
namespace: Tests
support_namespace: Support
paths:
tests: tests
output: tests/_output
data: tests/Support/Data
support: tests/Support
params:
- tests/.env
Suite Configs
tests/Integration.suite.yml— Integration suite configuration.tests/EndToEnd.suite.yml— E2E suite configuration.
Environment Variables (tests/.env)
Copied from tests/.dist.env. Contains database credentials and WordPress test config for wp-browser.
Writing Integration Tests
File and Namespace Convention
tests/Integration/<Name>Test.php
→ Tests\Integration\<Name>Test
Or with subdirectories:
tests/Integration/<SubDir>/<Name>Test.php
→ Tests\Integration\<SubDir>\<Name>Test
Test Class Structure
<?php
namespace Tests\Integration;
use WP_UnitTestCase;
use A8C\SpecialProjects\RestrictMediaFileAccess\AttachmentsFileManager;
class AttachmentsFileManagerTest extends WP_UnitTestCase {
private AttachmentsFileManager $file_manager;
public function setUp(): void {
parent::setUp();
$this->file_manager = new AttachmentsFileManager();
}
public function tearDown(): void {
parent::tearDown();
}
public function test_set_file_as_protected_returns_true_for_new_restriction(): void {
$attachment_id = self::factory()->attachment->create_upload_object( ... );
$result = $this->file_manager->set_file_as_protected( $attachment_id );
$this->assertTrue( $result );
}
public function test_set_file_as_protected_returns_false_when_already_protected(): void {
$attachment_id = self::factory()->attachment->create_upload_object( ... );
$this->file_manager->set_file_as_protected( $attachment_id );
$result = $this->file_manager->set_file_as_protected( $attachment_id );
$this->assertFalse( $result );
}
}
Key Testing Patterns
Reset singleton between tests:
Plugin is a singleton. Reset via reflection when needed:
$reflection = new \ReflectionClass( Plugin::class );
$property = $reflection->getProperty( 'instance' );
$property->setAccessible( true );
$property->setValue( null, null );
Test file operations with the WordPress attachment factory:
$attachment_id = self::factory()->attachment->create_upload_object(
'/path/to/test/image.jpg',
0 // parent post ID
);
Verify post meta:
$this->assertSame( '1', get_post_meta( $attachment_id, '_restricted_file', true ) );
$this->assertNotEmpty( get_post_meta( $attachment_id, '_protected_file_hash', true ) );
Test URL modifications:
$url = wp_get_attachment_url( $attachment_id );
$this->assertStringContains( 'protected-files/', $url );
Save/restore globals:
Session and file-serving code may read $_GET, $_SERVER. Save and restore in setUp/tearDown:
private array $original_server;
public function setUp(): void {
parent::setUp();
$this->original_server = $_SERVER;
}
public function tearDown(): void {
$_SERVER = $this->original_server;
parent::tearDown();
}
No placeholder assertions:
Every test method must have a meaningful assertion. Do not use $this->assertTrue( true ) as a placeholder.
Test Naming Convention
test_<action>_<condition>_<expected_result>
Examples:
test_set_file_as_protected_returns_true_for_new_filetest_set_file_as_unprotected_returns_false_when_not_restrictedtest_modify_attachment_url_returns_hash_url_for_protected_filetest_handle_protected_file_returns_gif_for_logged_out_usertest_restrict_file_endpoint_returns_403_when_disabled
Procedure: Adding Tests for a New Feature
- Determine if the feature needs integration tests, E2E tests, or both.
- Integration: Create
tests/Integration/<Name>Test.php. Test the PHP logic in isolation (file operations, meta, hooks, filters, REST endpoints). - E2E (Codeception): Create
tests/EndToEnd/<Name>Cest.php. Test user-facing behavior in the browser (media library, restriction toggle, file access). - Follow the naming and namespace conventions above.
- Run the full suite to verify no regressions:
npm run tests:run.
Verification
- All tests pass:
composer tests:run:integration. - No placeholder assertions (
assertTrue( true )). - New code has corresponding test coverage.
- Tests are isolated — no test depends on another test's side effects.
- Global state (
$_GET,$_SERVER) is saved/restored. - Singletons are properly reset between tests.