1

In a symfony 4 project, many services/controllers need log. Trying to use the advantage of traits & autowire options given by symfony, I created a loggerTrait that will be passed to the different services.

namespace App\Helper;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

trait LoggerTrait
{
    /** @var LoggerInterface */
    private $logger;

    /** @var array */
    private $context = [];

    /**
     * @return LoggerInterface
     */
    public function getLogger(): LoggerInterface
    {
        return $this->logger;
    }

    /**
     * @required
     *
     * @param LoggerInterface|null $logger
     */
    public function setLogger(?LoggerInterface $logger): void
    {
        $this->logger = $logger;
    }

    public function logDebug(string $message, array $context = []): void
    {
        $this->log(LogLevel::DEBUG, $message, $context);
    }
...
}

(inspired by symfonycasts.com)

A service will be using this trait

namespace App\Service;

use App\Helper\LoggerTrait;

class BaseService
{
    use LoggerTrait;

    /** @var string */
    private $name;

    public function __construct(string $serviceName)
    {
        $this->name = $serviceName;
    }

    public function logName()
    {
        $this->logInfo('Name of the service', ['name' => $this->name]);
    }
}

It works perfectly but I couldn't succeed to test it.

I tried to extend KernelTestCase in my test to mock an loggerInterface but I receive Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: The "Psr\Log\LoggerInterface" service is private, you cannot replace it which make perfect sens.

Here my test:

namespace App\Tests\Service;

use App\Service\BaseService;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class BaseServiceTest extends KernelTestCase
{
    private function loggerMock()
    {
        return $this->createMock(LoggerInterface::class);
    }

    protected function setUp()
    {
        self::bootKernel();
    }

    /**
     * @test
     * @covers ::logName
     */
    public function itShouldLogName()
    {
        // returns the real and unchanged service container
        $container = self::$kernel->getContainer();

        // gets the special container that allows fetching private services
        $container = self::$container;

        $loggerMock = $this->loggerMock();
        $loggerMock->expect(self::once())
            ->method('log')
            ->with('info', 'Name of the service', ['name' => 'service_test']);

        $this->logger = $container->set(LoggerInterface::class, $loggerMock);


        $baseService = new BaseService('service_test');
        var_dump($baseService->getLogger());
    }
}

Is there a solution to test such a logger inside the service ?

4
  • $baseService->setLogger($loggerMock) should do the trick. No need to fool around with the container or to test the @required functionality. Commented Aug 23, 2019 at 20:10
  • Is it okay to use it in the test even if do not use it in the service ? Commented Aug 26, 2019 at 7:54
  • 1
    setLogger is used in the service. It is just automatically called for you (thanks to @required) by the container. As a general rule, avoid the container for regular unit tests. Commented Aug 26, 2019 at 12:55
  • ok, thank you for confirming this! Commented Aug 26, 2019 at 14:44

1 Answer 1

0

You can override the service to be public (only for the test environment) in your config_test.yml as follows:

services:
  Psr\Log\LoggerInterface:
    public: true

This is commonly done for testing private services.

Sign up to request clarification or add additional context in comments.

1 Comment

I tried this, but it did not change the service.. I still have "Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: The "Psr\Log\LoggerInterface" service is private, you cannot replace it." error.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.