Skip to content

[FrameworkBundle] Add LoggerAssertionsTrait which provide shortcuts to assert a log has been written #51696

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ CHANGELOG
6.4
---

* Add `LoggerAssertionsTrait`
* Add `AbstractController::renderBlock()` and `renderBlockView()`
* Add native return type to `Translator` and to `Application::reset()`
* Deprecate the integration of Doctrine annotations, either uninstall the `doctrine/annotations` package or disable the integration by setting `framework.annotations` to `false`
Expand Down
1 change: 1 addition & 0 deletions src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
*/
abstract class KernelTestCase extends TestCase
{
use LoggerAssertionsTrait;
use MailerAssertionsTrait;
use NotificationAssertionsTrait;

Expand Down
64 changes: 64 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Test/LoggerAssertionsTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\Test;

use Monolog\Handler\TestHandler;
use Monolog\Logger;
use Monolog\LogRecord;

trait LoggerAssertionsTrait
{
public static function assertLogExists(string $expectedLog, string $level = Logger::DEBUG): void
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The signature is wrong. Monolog's level constants on the Logger class are integers, not strings.

And they are deprecated in favor of the enum in 3.x.

and in 3.x, the various hasRecord* methods of theTestHandler expect the Level enum.

{
self::ensureMonologHandlerIsAvailable();

/** @var TestHandler $logger */
$logger = self::getContainer()->get('monolog.handler.test');

self::assertTrue($logger->hasRecordThatPasses(
function (array|LogRecord $record) use ($expectedLog) {
return $record['message'] === $expectedLog;
},
$level,
));
}

public static function assertLogMatches(string $expectedRegex, string $level = Logger::DEBUG): void
{
self::ensureMonologHandlerIsAvailable();

/** @var TestHandler $logger */
$logger = self::getContainer()->get('monolog.handler.test');

self::assertTrue($logger->hasRecordThatMatches($expectedRegex, $level));
}

public static function assertLogContains(string $expectedLog, string $level = Logger::DEBUG): void
{
self::ensureMonologHandlerIsAvailable();

/** @var TestHandler $logger */
$logger = self::getContainer()->get('monolog.handler.test');

self::assertTrue($logger->hasRecordThatContains($expectedLog, $level));
}

/**
* @internal
*/
private static function ensureMonologHandlerIsAvailable(): void
{
if (!self::getContainer()->has('monolog.handler.test')) {
self::fail('The "monolog.handler.test" service is not available. Try registering the service "Monolog\Handler\TestHandler" as "monolog.handler.test" in your test configuration.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about mentioning using the Monolog bundle configuration if available?

when@test:
    monolog:
        handlers:
            test: { type: test }
            main:
                type: fingers_crossed
                # […]

This would work, because it'll be registered through the Monolog bundle extension in the handlers chain, but I don't get how just registering a monolog.handler.test would ensure this logger handler is used when using the logger in your application.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, I should belongs to the Monolog bridge, as it only works with Monolog. Or I have to reimplement hasRecord* methods for generic loggers (which I do not want to do 😄).

I'm going to move it, thanks for your feedback

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't well know this part, especially how to use this bridge in tests. Would you help me?

}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller;

use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Response;

final class LoggerController
{
public function index(LoggerInterface $logger)
{
$logger->debug('test1_'.__CLASS__);
$logger->debug('test2_'.__CLASS__);
$logger->debug('test3_'.__CLASS__);

return new Response();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Logger;

use Monolog\Logger;

class TestLogger extends Logger
{
public array $logs = [];

public function __construct($handler)
{
parent::__construct(__CLASS__, [$handler]);
}

public function log($level, $message, array $context = []): void
{
$this->logs[] = [
'level' => $level,
'message' => (string) $message,
'context' => $context,
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Logger;

use Monolog\Logger;

class TestLoggerWithoutHandler extends Logger
{
public array $logs = [];

public function __construct()
{
parent::__construct(__CLASS__);
}

public function log($level, $message, array $context = []): void
{
$this->logs[] = [
'level' => $level,
'message' => (string) $message,
'context' => $context,
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,7 @@ uid:
send_notification:
path: /send_notification
defaults: { _controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\NotificationController::indexAction }

log:
path: /log
defaults: { _controller: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\LoggerController::index }
51 changes: 51 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Tests/Functional/LoggerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\Tests\Functional;

use PHPUnit\Framework\AssertionFailedError;

final class LoggerTest extends AbstractWebTestCase
{
public function testLoggerAssertion()
{
$client = $this->createClient(['test_case' => 'Logger', 'root_config' => 'config.yml', 'debug' => true]);
$client->request('GET', '/log');

$this->assertLogExists('test1_Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\LoggerController');
$this->assertLogMatches('/(test2_).*(LoggerController)/');
$this->assertLogContains('test3');
}

public function testLoggerAssertionWithoutTestHandler()
{
$client = $this->createClient(['test_case' => 'LoggerWithoutHandler', 'root_config' => 'config.yml', 'debug' => true]);
$client->request('GET', '/log');

try {
$this->assertLogExists('test1_Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\LoggerController');
} catch (AssertionFailedError $e) {
$this->assertSame('The "monolog.handler.test" service is not available. Try registering the service "Monolog\Handler\TestHandler" as "monolog.handler.test" in your test configuration.', $e->getMessage());
}

try {
$this->assertLogMatches('/(test2_).*(LoggerController)/');
} catch (AssertionFailedError $e) {
$this->assertSame('The "monolog.handler.test" service is not available. Try registering the service "Monolog\Handler\TestHandler" as "monolog.handler.test" in your test configuration.', $e->getMessage());
}

try {
$this->assertLogContains('test3');
} catch (AssertionFailedError $e) {
$this->assertSame('The "monolog.handler.test" service is not available. Try registering the service "Monolog\Handler\TestHandler" as "monolog.handler.test" in your test configuration.', $e->getMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestBundle;

return [
new FrameworkBundle(),
new TestBundle(),
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
imports:
- { resource: ../config/default.yml }
- { resource: services.yml }

framework:
profiler: ~
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_loggertest_bundle:
resource: '@TestBundle/Resources/config/routing.yml'
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
services:
_defaults:
public: true

Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\LoggerController:
tags: ['controller.service_arguments']

monolog.handler.test:
class: Monolog\Handler\TestHandler

logger:
class: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Logger\TestLogger
arguments: ['@monolog.handler.test']
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\TestBundle;

return [
new FrameworkBundle(),
new TestBundle(),
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
imports:
- { resource: ../config/default.yml }
- { resource: services.yml }

framework:
profiler: ~
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_loggertest_bundle:
resource: '@TestBundle/Resources/config/routing.yml'
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
_defaults:
public: true

Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Controller\LoggerController:
tags: ['controller.service_arguments']

logger:
class: Symfony\Bundle\FrameworkBundle\Tests\Functional\Bundle\TestBundle\Logger\TestLoggerWithoutHandler