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;
}
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);
}
}
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
];
}
}
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