symfony/mercure

Improve testing integration

rvitaliy opened this issue · 5 comments

Hi!

My problem is that Symfony\Component\Mercure\Publisher is final, so i can't mock it with Prophecy\Prophecy\ObjectProphecy.
I suggest to add an interface and use it as typehint instead of concrete final class.

what do you think?

As it's just an invokable class (__invoke), you can mock it using any callable (a function, a closure, an invokable class...).

I can't understand how a invocable class can resolve my issue.
I try to write my test example to explain it better.
NB: I prefer use Publisher as a service and don't hard code it on my services.

my service class:

<?php

declare(strict_types=1);

use Symfony\Component\Mercure\Publisher;
use Symfony\Component\Mercure\Update;

final class MyClass
{
    /**
     * @var Publisher
     */
    private $publisher;

    public function __construct(Publisher $publisher)
    {
        $this->publisher = $publisher;
    }

    public function execute(bool $notifyToCustomer): void
    {
        if ($notifyToCustomer) {
            $this->publisher->__invoke(
                new Update(
                    'topic',
                    'data',
                    ['target']
                )
            );
        }
    }
}

my test class:

<?php

declare(strict_types=1);

use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\Prophecy\ObjectProphecy;
use Symfony\Component\Mercure\Publisher;

final class MyClassTest extends TestCase
{
    /**
     * @var MyClass
     */
    private $myClass;

    /**
     * @var ObjectProphecy<Publisher>
     */
    private $publisher;

    protected function setUp(): void
    {
        parent::setUp();

        $this->publisher = $this->prophesize(Publisher::class);

        $this->myClass = new MyClass(
            $this->publisher->reveal()
        );
    }

    public function testExecuteNotifyToCustomer(): void
    {
        $this->publisher
            ->__invoke(Argument::cetera())
            ->shouldBeCalledOnce();

        $this->myClass->execute(true);
    }

    public function testExecuteDontNotifyToCustomer(): void
    {
        $this->publisher
            ->__invoke(Argument::cetera())
            ->shouldNotBeCalled();

        $this->myClass->execute(false);
    }
}

@dunglas please, do you have any suggestions?

@rvitaliy Create your own interface PublisherInterface for example with a method 'publish', then create an implementation of that interface and inject publisher (mercure instance).

class MyPublisher implement PublisherInterface
{
  public function __construct(Mercure\Namespace\Publisher $publisher)
  {
   $this->publisher = $publisher;
  }

  public function publish($message)
  {
    $this->publisher->__invoke($message);
  }

}

Then in your test, mock the interface...

Hi @spike31, thanks for suggestion!
It's exactly what i do, but i can't cover with tests MyPublisher implementation.