Testing & Debugging
When to Use
Test plugin architectures at unit level for plugin managers and service collectors. Use functional tests for REST API endpoints and integration workflows.
Decision
| Test Type | Use When | Why |
|---|---|---|
| Unit tests | Plugin manager logic | Isolate plugin discovery and instantiation |
| Unit tests | Service collector aggregation | Test service collection without container |
| Unit tests | Provider proxy event dispatching | Verify event flow without external dependencies |
| Functional tests | REST API endpoints | Test full HTTP request/response cycle |
| Functional tests | Plugin integration workflows | Verify multi-plugin coordination |
Pattern
Foundation Pattern Testing:
Reference: Commerce Payment module tests
<?php
namespace Drupal\Tests\foundation_module\Unit;
use PHPUnit\Framework\TestCase;
class ServiceGatewayTest extends TestCase {
public function testGatewayExecution() {
$gateway = $this->createMockGateway();
// Test supported method types
$method_types = $gateway->getSupportedMethodTypes();
$this->assertContains('example_method', $method_types);
// Test operation execution
$result = $gateway->executeOperation('example_method', ['test' => 'data']);
$this->assertTrue($result['success']);
}
}
Provider Pattern Testing:
Reference: AI module provider tests pattern
<?php
namespace Drupal\Tests\my_module\Unit;
use PHPUnit\Framework\TestCase;
class ServiceProviderProxyTest extends TestCase {
public function testProxyEventDispatching() {
$provider = $this->createMock(ServiceProviderInterface::class);
$eventDispatcher = $this->createMock(EventDispatcherInterface::class);
// Verify events are dispatched
$eventDispatcher->expects($this->exactly(2))
->method('dispatch')
->withConsecutive(
[$this->isInstanceOf(PreServiceEvent::class)],
[$this->isInstanceOf(PostServiceEvent::class)]
);
$proxy = new ServiceProviderProxy($provider, $eventDispatcher, $this->loggerFactory, $this->uuid);
$proxy->executeService(new ServiceInput([]), 'config');
}
}
Service Collector Pattern Testing:
Orchestration ships no test directory, so there is no reference implementation to read for this pattern. The test below is written from the pattern itself.
<?php
namespace Drupal\Tests\orchestration\Unit;
use PHPUnit\Framework\TestCase;
class ServicesProviderManagerTest extends TestCase {
public function testServiceCollection() {
$manager = new ServicesProviderManager();
$provider = $this->createMock(ServicesProviderInterface::class);
$provider->method('getId')->willReturn('test_provider');
$provider->method('getAll')->willReturn([
new Service($provider, 'test_service', 'Test Service', 'Description')
]);
$manager->addServicesProvider($provider);
$services = $manager->getAllServices();
$this->assertCount(1, $services);
$this->assertEquals('test_provider::test_service', array_key_first($services));
}
public function testServiceExecution() {
$manager = new ServicesProviderManager();
$provider = $this->createMock(ServicesProviderInterface::class);
$provider->method('getId')->willReturn('test');
$provider->method('execute')->willReturn(['result' => 'success']);
$service = new Service($provider, 'svc', 'Service', 'Desc');
$provider->method('getAll')->willReturn([$service]);
$manager->addServicesProvider($provider);
$result = $manager->executeService('test::svc', ['param' => 'value']);
$this->assertEquals(['result' => 'success'], $result);
}
}
REST API Testing Pattern:
<?php
namespace Drupal\Tests\orchestration\Functional;
use Drupal\Tests\BrowserTestBase;
class RestApiTest extends BrowserTestBase {
protected static $modules = ['orchestration', 'orchestration_eca'];
public function testServicesEndpoint() {
$this->drupalLogin($this->drupalCreateUser(['use orchestration']));
$response = $this->drupalGet('/orchestration/services', [], ['Accept' => 'application/json']);
$this->assertSession()->statusCodeEquals(200);
$services = json_decode($response, TRUE);
$this->assertIsArray($services);
$this->assertArrayHasKey('id', $services[0]);
$this->assertArrayHasKey('label', $services[0]);
$this->assertArrayHasKey('config', $services[0]);
}
public function testServiceExecution() {
$this->drupalLogin($this->drupalCreateUser(['use orchestration']));
$data = [
'id' => 'eca::test_service',
'config' => ['param1' => 'value1']
];
$response = $this->drupalPost(
'/orchestration/service/execute',
json_encode($data),
['Content-Type' => 'application/json']
);
$this->assertSession()->statusCodeEquals(200);
$result = json_decode($response, TRUE);
$this->assertNotEmpty($result);
}
}
Common Mistakes
- Wrong: Testing only plugin discovery → Right: Test both discovery and instantiation with configuration
- Wrong: No mock for external APIs in provider tests → Right: Mock external dependencies to avoid network calls
- Wrong: Functional tests without proper permissions → Right: Create user with correct permissions for REST endpoint access
See Also
- Foundation + Extension Pattern
- Provider Plugin Pattern
- Service Collector Pattern
- Reference: Commerce Payment module tests
- Reference: PHPUnit in Drupal
- Reference: REST API Testing