DEV Community

Nacho Colomina Torregrosa
Nacho Colomina Torregrosa

Posted on • Edited on

Using PHP anonymous classes to test collection services

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 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

interface NotificationHandlerInterface {

    public function send(mixed $data): void;
    public function getName(): string;
}
Enter fullscreen mode Exit fullscreen mode

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

  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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

If you like my content and have learned with this article, you can take a look to my book: https://amzn.eu/d/3eO1DDi

Top comments (0)