Claude
Skills
Sign in
Back

wordpress-testing-qa

Included with Lifetime
$97 forever

WordPress plugin and theme testing with PHPUnit integration tests, WP_Mock unit tests, PHPCS coding standards, and CI/CD workflows

Web Dev

What this skill does

# WordPress Testing & Quality Assurance

---
progressive_disclosure:
  entry_point:
    summary: "WordPress plugin and theme testing with PHPUnit, WP_Mock, PHPCS, and CI/CD for quality assurance"
    when_to_use:
      - "Testing WordPress plugins with PHPUnit integration tests"
      - "Unit testing without loading WordPress core (WP_Mock)"
      - "Enforcing coding standards with PHPCS"
    quick_start:
      - "Set up PHPUnit with WordPress test suite"
      - "Write unit tests with WP_Mock"
      - "Configure PHPCS with WPCS ruleset"
---

## Testing Strategy

### Testing Pyramid for WordPress

**The WordPress Testing Hierarchy:**

```
       /\
      /  \     E2E Tests (Playwright)
     /    \    - Full user workflows
    /------\   - Browser automation
   /        \
  /  INTEG  \  Integration Tests (PHPUnit + WordPress)
 /    TESTS  \ - Database operations
/            \ - Hook interactions
--------------
 UNIT TESTS    Unit Tests (WP_Mock)
               - Pure logic
               - No WordPress dependency
```

**Test Distribution Guidelines:**
- **Unit Tests (60%):** Fast, isolated, no WordPress
  - Pure PHP functions
  - Class methods with clear inputs/outputs
  - Business logic without side effects
- **Integration Tests (30%):** WordPress-loaded tests
  - Database operations
  - Hook/filter interactions
  - Custom post type registration
  - Settings API functionality
- **E2E Tests (10%):** Browser automation
  - Critical user workflows
  - Admin panel interactions
  - Frontend form submissions

### When to Use PHPUnit vs WP_Mock

**Use PHPUnit (Integration Tests) when:**
- ✅ Testing database operations (`$wpdb`, post creation, meta data)
- ✅ Testing WordPress hooks (actions/filters actually firing)
- ✅ Testing template rendering and output
- ✅ Testing plugin activation/deactivation logic
- ✅ Testing with actual WordPress functions

**Use WP_Mock (Unit Tests) when:**
- ✅ Testing pure business logic
- ✅ Testing functions that call WordPress functions but logic is independent
- ✅ Need fast test execution (no database setup)
- ✅ Testing in isolation without side effects
- ✅ Mocking external API calls

### Test Coverage Goals

**Minimum Coverage Requirements:**
- **New Code:** 80% minimum coverage
- **Critical Paths:** 95% coverage (payment processing, authentication, data validation)
- **Legacy Code:** Gradual improvement, prioritize high-risk areas
- **Public APIs:** 100% coverage for all public methods

**What to Test (Priority Order):**
1. **Security Functions:** Nonce verification, sanitization, capability checks
2. **Data Operations:** Database CRUD, data validation, transformation
3. **Business Logic:** Calculations, workflows, state transitions
4. **Hook Callbacks:** Action/filter handlers
5. **Public APIs:** REST endpoints, WP-CLI commands

**What NOT to Test:**
- ❌ WordPress core functions (assume they work)
- ❌ Third-party library internals
- ❌ Simple getters/setters with no logic
- ❌ Configuration files (theme.json, block.json)

---

## PHPUnit Integration Testing

### WordPress Test Suite Setup

**Step 1: Install Dependencies**

```bash
# Install PHPUnit and WordPress polyfills
composer require --dev phpunit/phpunit "^9.6"
composer require --dev yoast/phpunit-polyfills "^2.0"

# Generate test scaffold with WP-CLI
wp scaffold plugin-tests my-plugin

# This creates:
# - tests/bootstrap.php
# - tests/test-sample.php
# - phpunit.xml.dist
# - bin/install-wp-tests.sh
```

**Step 2: Install WordPress Test Library**

```bash
# Install WordPress test suite and test database
# Syntax: bash bin/install-wp-tests.sh <db-name> <db-user> <db-pass> <db-host> <wp-version>
bash bin/install-wp-tests.sh wordpress_test root '' localhost latest

# For specific WordPress version:
bash bin/install-wp-tests.sh wordpress_test root '' localhost 6.7
```

**Step 3: Configure phpunit.xml.dist**

```xml
<?xml version="1.0"?>
<phpunit
    bootstrap="tests/bootstrap.php"
    backupGlobals="false"
    colors="true"
    convertErrorsToExceptions="true"
    convertNoticesToExceptions="true"
    convertWarningsToExceptions="true"
    stopOnFailure="false"
>
    <testsuites>
        <testsuite name="plugin">
            <directory prefix="test-" suffix=".php">./tests/</directory>
            <exclude>./tests/bootstrap.php</exclude>
        </testsuite>
    </testsuites>

    <coverage includeUncoveredFiles="true">
        <include>
            <directory suffix=".php">./includes/</directory>
        </include>
        <exclude>
            <directory>./vendor/</directory>
            <directory>./tests/</directory>
        </exclude>
        <report>
            <html outputDirectory="coverage-html"/>
            <text outputFile="php://stdout" showOnlySummary="true"/>
        </report>
    </coverage>

    <php>
        <const name="WP_TESTS_PHPUNIT_POLYFILLS_PATH" value="vendor/yoast/phpunit-polyfills"/>
    </php>
</phpunit>
```

### WP_UnitTestCase Base Class

**tests/bootstrap.php:**

```php
<?php
/**
 * PHPUnit bootstrap file
 */

// Composer autoloader
require_once dirname(__DIR__) . '/vendor/autoload.php';

// WordPress tests directory
$_tests_dir = getenv('WP_TESTS_DIR');
if (!$_tests_dir) {
    $_tests_dir = rtrim(sys_get_temp_dir(), '/\\') . '/wordpress-tests-lib';
}

if (!file_exists("{$_tests_dir}/includes/functions.php")) {
    throw new Exception("Could not find {$_tests_dir}/includes/functions.php");
}

// Give access to tests_add_filter() function
require_once "{$_tests_dir}/includes/functions.php";

/**
 * Manually load the plugin being tested
 */
function _manually_load_plugin() {
    require dirname(__DIR__) . '/my-plugin.php';
}
tests_add_filter('muplugins_loaded', '_manually_load_plugin');

// Start up the WordPress testing environment
require "{$_tests_dir}/includes/bootstrap.php";
```

### Factory Objects for Test Data

**Using Built-in Factories:**

```php
<?php
class Test_Plugin_Integration extends WP_UnitTestCase {

    /**
     * Test creating posts with factory
     */
    public function test_create_post_with_meta() {
        // Create a post using factory
        $post_id = $this->factory->post->create([
            'post_title'   => 'Test Post',
            'post_content' => 'Test content for integration test',
            'post_status'  => 'publish',
            'post_type'    => 'post',
        ]);

        $this->assertIsInt($post_id);
        $this->assertGreaterThan(0, $post_id);

        // Add post meta
        add_post_meta($post_id, '_custom_field', 'custom_value');

        // Verify meta was saved
        $meta_value = get_post_meta($post_id, '_custom_field', true);
        $this->assertEquals('custom_value', $meta_value);
    }

    /**
     * Test creating users
     */
    public function test_user_can_edit_post() {
        // Create editor user
        $editor_id = $this->factory->user->create([
            'role' => 'editor',
            'user_login' => 'test_editor',
            'user_email' => '[email protected]',
        ]);

        // Set as current user
        wp_set_current_user($editor_id);

        // Create post
        $post_id = $this->factory->post->create([
            'post_author' => $editor_id,
        ]);

        // Test capabilities
        $this->assertTrue(current_user_can('edit_post', $post_id));
        $this->assertTrue(current_user_can('edit_posts'));
        $this->assertFalse(current_user_can('manage_options'));
    }

    /**
     * Test creating terms and taxonomy
     */
    public function test_assign_categories() {
        // Create category
        $category_id = $this->factory->category->create([
            'name' => 'Test Category',
            'slug' => 'test-category',
        ]);

        // Create post
        $post_id = $this->factory->post->create();

        // Assign category
        wp_set_post_categories($post_id, [$category_id]);

        // Verify assignment
        $categories = wp_get_post_categories($post_id);
        $this->assertContains($category_id, $categories);

Related in Web Dev