Skip to content

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