如何断言 phpunit 测试是否写入某个日志条目/如何断言是否已发送电子邮件

How to assert if a phpunit test writes a certain log entry / how to assert if emails are sent

我在 php8 上的 symfony5 项目中有一项服务 class,它按照特定规则发送电子邮件。通过我的测试,我想检查是否发送了正确的邮件。这个任务存在于几个项目中,所以我真的很想为此找到一个解决方案。

收集邮件收件人的方法目前是私有的,你不应该暴露你的隐私。

一个想法是为每个潜在的邮件接收者写一个日志条目。但是如何检查我的 test.log 文件中的某些值?

我找到了一个包,它扩展了 phpunit 只是为了这个目的(https://github.com/phoenixrvd/phpunit-assert-log-entry),但它看起来不像是维护的。

你知道任何一个的解决方案吗

目前我使用 Trait 向我的服务添加日志记录功能,它有一个 setter 方法,它通过 @required 注释设置记录器。


namespace App\Logger;

use Psr\Log\LoggerInterface;

trait LoggerTrait
{
    private $logger;

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

    protected function logInfo(string $message, array $context = [])
    {
        if (null !== $this->logger) {
            $this->logger->info($message, $context);
        }
    }

依赖注入的一大优势 - 传递依赖,而不是按需创建它们 - 是你可以用 test doubles - 存根、模拟和间谍。 PHPUnit supports test doubles out of the box 应有尽有。

要断言特定行已记录,您需要:

  • 创建 LoggerInterface
  • 的模拟实现
  • 告诉 mock 它应该期待对 info 方法的特定调用
  • 将模拟设置为您要测试的class实例的记录器

例如:

public function testLogListsEmailAddress() {
    // Create the mock
    $mockLogger = $this->createMock(LoggerInterface::class);
    // Expect a call to info, with the e-mail address in the message
    $mockLogger->expects('info')
       ->with($this->stringContains('user@example.com');
    
    // Set up the class to test
    $classUnderTest = new MyThingummy;
    $classUnderTest->setLogger($mockLogger);

    // Run the code which should log the address
    $classUnderTest->doSomething('user', 'example.com');
}

断言发送特定电子邮件的方式类似:实际发送电子邮件的 class 应该是决定发送哪些电子邮件的 class 的依赖项发送。所以你可能有这样的东西:

public function testLogSendsEmail() {
    $mockEmailSender = $this->createMock(EmailSenderInterface::class);
    $mockEmailSender->expects('send')
       // first argument to send() must be expected address; others can be anything
       ->with('user@example.com', $this->anything());
    
    // Perhaps in this case, the dependency is passed to the constructor
    $classUnderTest = new MyThingummy($mockEmailSender);

    $classUnderTest->doSomething('user', 'example.com');
}