Testing
This guide covers testing WPZylos Scaffold plugins.
Setup
The scaffold includes PHPUnit for testing. Tests are located in tests/.
Directory Structure
tests/
+-- Unit/ # Unit tests
+-- Feature/ # Feature/integration tests (create as needed)
+-- bootstrap.php # Test bootstrap
Running Tests
# Run all tests
composer test
# Or directly
./vendor/bin/phpunit
# Run specific test file
./vendor/bin/phpunit tests/Unit/ExampleTest.php
# Run specific test method
./vendor/bin/phpunit --filter testMethodName
# Run with coverage (requires Xdebug)
./vendor/bin/phpunit --coverage-html coverage/
Writing Tests
Basic Unit Test
<?php
// tests/Unit/PluginContextTest.php
namespace MyPlugin\Tests\Unit;
use PHPUnit\Framework\TestCase;
use MyPlugin\Core\PluginContext;
class PluginContextTest extends TestCase
{
private PluginContext $context;
protected function setUp(): void
{
parent::setUp();
$this->context = PluginContext::create([
'file' => '/path/to/plugin.php',
'slug' => 'test-plugin',
'prefix' => 'testplugin_',
'textDomain' => 'test-plugin',
'version' => '1.0.0',
]);
}
public function testSlugReturnsCorrectValue(): void
{
$this->assertEquals('test-plugin', $this->context->slug());
}
public function testPrefixReturnsCorrectValue(): void
{
$this->assertEquals('testplugin_', $this->context->prefix());
}
public function testHookPrefixesName(): void
{
$this->assertEquals('testplugin_init', $this->context->hook('init'));
}
public function testOptionKeyPrefixesKey(): void
{
$this->assertEquals('testplugin_settings', $this->context->optionKey('settings'));
}
public function testMetaKeyPrefixesWithUnderscore(): void
{
$this->assertEquals('_testplugin_data', $this->context->metaKey('data'));
}
}
Testing with Mocks
<?php
// tests/Unit/ItemServiceTest.php
namespace MyPlugin\Tests\Unit;
use PHPUnit\Framework\TestCase;
use MyPlugin\Services\ItemService;
use WPZylos\Framework\Database\Connection;
use WPZylos\Framework\Core\Contracts\ContextInterface;
class ItemServiceTest extends TestCase
{
public function testAllReturnsArrayOfItems(): void
{
// Create mocks
$db = $this->createMock(Connection::class);
$context = $this->createMock(ContextInterface::class);
// Configure mocks
$context->method('tableName')
->with('items')
->willReturn('wp_test_items');
$db->method('getResults')
->willReturn([
(object) ['id' => 1, 'name' => 'Item 1'],
(object) ['id' => 2, 'name' => 'Item 2'],
]);
// Create service
$service = new ItemService($db, $context);
// Assert
$items = $service->all();
$this->assertCount(2, $items);
$this->assertEquals('Item 1', $items[0]->name);
}
public function testFindReturnsItem(): void
{
$db = $this->createMock(Connection::class);
$context = $this->createMock(ContextInterface::class);
$context->method('tableName')->willReturn('wp_test_items');
$db->method('prepare')->willReturn('SELECT * FROM wp_test_items WHERE id = 1');
$db->method('getRow')->willReturn((object) ['id' => 1, 'name' => 'Item 1']);
$service = new ItemService($db, $context);
$item = $service->find(1);
$this->assertEquals(1, $item->id);
}
public function testFindReturnsNullWhenNotFound(): void
{
$db = $this->createMock(Connection::class);
$context = $this->createMock(ContextInterface::class);
$context->method('tableName')->willReturn('wp_test_items');
$db->method('prepare')->willReturn('');
$db->method('getRow')->willReturn(null);
$service = new ItemService($db, $context);
$this->assertNull($service->find(999));
}
}
Testing Controllers
<?php
// tests/Unit/DashboardControllerTest.php
namespace MyPlugin\Tests\Unit;
use PHPUnit\Framework\TestCase;
use MyPlugin\Http\Controllers\DashboardController;
use WPZylos\Framework\Views\ViewFactory;
use WPZylos\Framework\Core\Contracts\ContextInterface;
class DashboardControllerTest extends TestCase
{
public function testIndexRendersView(): void
{
$view = $this->createMock(ViewFactory::class);
$context = $this->createMock(ContextInterface::class);
$context->method('textDomain')->willReturn('test-plugin');
$view->expects($this->once())
->method('render')
->with('admin/dashboard', $this->isType('array'))
->willReturn('<div>Dashboard</div>');
$controller = new DashboardController($view, $context);
ob_start();
$controller->index();
$output = ob_get_clean();
$this->assertStringContainsString('Dashboard', $output);
}
}
Code Coverage
Generate HTML coverage report:
./vendor/bin/phpunit --coverage-html coverage/
View the report by opening coverage/index.html in a browser.
Continuous Integration
GitHub Actions Example
# .github/workflows/tests.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
php: ["8.0", "8.1", "8.2"]
steps:
- uses: actions/checkout@v3
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: json, mbstring
coverage: xdebug
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Run tests
run: composer test
Best Practices
- Keep tests isolated — Each test should be independent
- Use descriptive names —
testMethodNameDoesExpectedBehavior - Test edge cases — Empty inputs, nulls, invalid data
- Mock external dependencies — Database, APIs, WordPress functions
- Aim for high coverage — Target 80%+ code coverage
Resources
- PHPUnit Documentation
- WordPress Plugin Testing
- wpzylos.com — Framework testing guide