Using PHP anonymous classes to test collection services
Tags: php, testing, phpunit, symfony
Published: 2023-03-15 18:59:21

Many times we hold classes which follows the same contract into collections. An example would be a collection which holds services which send a notification to the user using different channels.

In this article we will see how to use [anonymous classes](https://www.php.net/manual/en/language.oop5.anonymous.php) to test we get the right service from a collection.

First of all, let's define the interface which our holded services will have to implement

```php
interface NotificationHandlerInterface {
    
    public function send(mixed $data): void;
    public function getName(): string;
}
```

Now, let's see how the service which looks into our collection looks like:

```php
  class NotificationHandlerDiscover {

    /**
     * @var NotificationHandlerInterface[]
     */
    private array $collection;

    public function __construct(iterable $collection) {
        $this->collection = $collection;
    }

    public function getHandler(string $name): NotificationHandlerInterface {
        $handlers = array_filter(
            $this->collection, 
            fn(NotificationHandlerInterface $handler) => $handler->getName() === $name
        );
        if(count($handlers) === 0){
            throw new \InvalidArgumentException('There is no service named ' . $name);
        }
        
        return array_pop($handlers);
    }
}
```

We don't mind how collections are loaded in _NotificationHandlerDiscover_ since we only want to test it gets the right service acording to passed _$name_. 
Let's see now how the test looks like

```php
class NotificationHandlerDiscoverTest extends TestCase
{
    public function testEmailHandler()
    {
        $notificationHandlerDiscover = new NotificationHandlerDiscover($this->getCollection());
        $emailHandler = $notificationHandlerDiscover->getHandler('email');

        $this->assertInstanceOf(NotificationHandlerInterface::class, $emailHandler);
        $this->assertEquals('email', $emailHandler->getName() );
    }

    public function testSmsHandler()
    {
        $notificationHandlerDiscover = new NotificationHandlerDiscover($this->getCollection());
        $emailHandler = $notificationHandlerDiscover->getHandler('sms');

        $this->assertInstanceOf(NotificationHandlerInterface::class, $emailHandler);
        $this->assertEquals('sms', $emailHandler->getName() );
    }

    public function testUnexistingHandler()
    {
        $this->expectException(\InvalidArgumentException::class);
        $notificationHandlerDiscover = new NotificationHandlerDiscover($this->getCollection());
        $notificationHandlerDiscover->getHandler('telegram');
    }

    /**
     * @return NotificationHandlerInterface[]
     */
    private function getCollection(): array
    {
        $handlerEmail = new class implements NotificationHandlerInterface {

            public function send(mixed $data): void {  }

            public function getName(): string
            {
                return 'email';
            }
        };

        $handlerSms = new class implements NotificationHandlerInterface {

            public function send(mixed $data): void {  }

            public function getName(): string
            {
                return 'sms';
            }
        };

        return [
            $handlerSms,
            $handlerEmail
        ];
    }
}
```

As we can see above, we also do not mind how handlers implentation works. We are testing if our discover handler gets the right service or throws the expected exception when such service does not exists. 

Using anonymous classes we can create an "on the fly" array which holds two classes which implements _NotificationHandlerInterface_. We only need to write _getName()_ method code which is used by the discover class to locate the service.