Integration Testing

Integration tests verify WPZylos components work correctly with WordPress.

Setup

Install wp-env

npm install -g @wordpress/env

Configure wp-env

// .wp-env.json
{
  "core": "WordPress/WordPress#6.4",
  "phpVersion": "8.0",
  "plugins": ["."],
  "config": {
    "WP_DEBUG": true,
    "SCRIPT_DEBUG": true
  }
}

Bootstrap File

<?php
// tests/Integration/bootstrap.php

$_tests_dir = getenv('WP_TESTS_DIR') ?: '/tmp/wordpress-tests-lib';

require_once $_tests_dir . '/includes/functions.php';

tests_add_filter('muplugins_loaded', function () {
    require dirname(__DIR__, 2) . '/vendor/autoload.php';
    require dirname(__DIR__, 2) . '/my-plugin.php';
});

require $_tests_dir . '/includes/bootstrap.php';

Writing Integration Tests

Database Test

<?php
// tests/Integration/Database/ConnectionTest.php

namespace Vendor\MyPlugin\Tests\Integration\Database;

use WP_UnitTestCase;
use WPZylos\Framework\Database\Connection;
use WPZylos\Framework\Core\PluginContext;

class ConnectionTest extends WP_UnitTestCase
{
    private Connection $db;
    private PluginContext $context;

    public function setUp(): void
    {
        parent::setUp();

        $this->context = new PluginContext(
            ABSPATH . 'wp-content/plugins/my-plugin/my-plugin.php',
            'my-plugin',
            'mp_',
            'my-plugin',
            '1.0.0'
        );

        $this->db = new Connection($this->context);

        // Create test table
        global $wpdb;
        $wpdb->query("CREATE TABLE IF NOT EXISTS {$wpdb->prefix}mp_test (
            id INT AUTO_INCREMENT PRIMARY KEY,
            name VARCHAR(255)
        )");
    }

    public function tearDown(): void
    {
        global $wpdb;
        $wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}mp_test");
        parent::tearDown();
    }

    public function testInsertReturnsId(): void
    {
        global $wpdb;

        $id = $this->db->insert($wpdb->prefix . 'mp_test', [
            'name' => 'Test Entry',
        ]);

        $this->assertIsInt($id);
        $this->assertGreaterThan(0, $id);
    }

    public function testUpdateModifiesRows(): void
    {
        global $wpdb;
        $table = $wpdb->prefix . 'mp_test';

        $id = $this->db->insert($table, ['name' => 'Original']);
        $affected = $this->db->update($table, ['name' => 'Updated'], ['id' => $id]);

        $this->assertSame(1, $affected);

        $row = $this->db->getRow("SELECT * FROM {$table} WHERE id = %d", $id);
        $this->assertSame('Updated', $row->name);
    }
}

Hook Manager Test

<?php
// tests/Integration/Hooks/HookManagerTest.php

namespace Vendor\MyPlugin\Tests\Integration\Hooks;

use WP_UnitTestCase;
use WPZylos\Framework\Hooks\HookManager;
use WPZylos\Framework\Core\PluginContext;

class HookManagerTest extends WP_UnitTestCase
{
    private HookManager $hooks;

    public function setUp(): void
    {
        parent::setUp();

        $context = new PluginContext(
            '/path/to/plugin.php',
            'test-plugin',
            'tp_',
            'test-plugin',
            '1.0.0'
        );

        $this->hooks = new HookManager($context);
    }

    public function testWpActionRegistersCallback(): void
    {
        $called = false;

        $this->hooks->wpAction('init', function () use (&$called) {
            $called = true;
        });

        do_action('init');

        $this->assertTrue($called);
    }

    public function testWpFilterModifiesValue(): void
    {
        $this->hooks->wpFilter('the_title', function ($title) {
            return $title . ' - Modified';
        });

        $result = apply_filters('the_title', 'Original');

        $this->assertSame('Original - Modified', $result);
    }
}

Container Integration Test

<?php
// tests/Integration/Container/ContainerIntegrationTest.php

namespace Vendor\MyPlugin\Tests\Integration\Container;

use WP_UnitTestCase;
use WPZylos\Framework\Container\Container;
use WPZylos\Framework\Core\PluginContext;
use WPZylos\Framework\Core\Contracts\ContextInterface;

class ContainerIntegrationTest extends WP_UnitTestCase
{
    public function testResolvesContextInterface(): void
    {
        $container = new Container();
        $context = new PluginContext('/path', 'slug', 'pf_', 'slug', '1.0.0');

        $container->instance(ContextInterface::class, $context);

        $resolved = $container->get(ContextInterface::class);

        $this->assertSame($context, $resolved);
        $this->assertSame('pf_', $resolved->prefix());
    }

    public function testAutoWiresWithWordPressDependencies(): void
    {
        $container = new Container();

        // A service that depends on WordPress functions working
        $container->singleton(WordPressAwareService::class);

        $service = $container->get(WordPressAwareService::class);

        $this->assertInstanceOf(WordPressAwareService::class, $service);
    }
}

Running Integration Tests

With wp-env

# Start environment
wp-env start

# Run tests
wp-env run tests-cli --env-cwd=/var/www/html/wp-content/plugins/my-plugin \
    vendor/bin/phpunit --testsuite=Integration

With Docker

docker-compose -f docker-compose.test.yml run --rm wordpress \
    vendor/bin/phpunit --testsuite=Integration

CI Configuration

# .github/workflows/tests.yml
integration-tests:
  runs-on: ubuntu-latest

  services:
    mysql:
      image: mysql:8.0
      env:
        MYSQL_ROOT_PASSWORD: root
        MYSQL_DATABASE: wordpress_test
      ports:
        - 3306:3306

  steps:
    - uses: actions/checkout@v4

    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: "8.0"

    - name: Install dependencies
      run: composer install

    - name: Install WordPress test suite
      run: bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 latest

    - name: Run integration tests
      run: vendor/bin/phpunit --testsuite=Integration