Skip to content
Advertisement

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

I have a service class in my symfony5 project on php8, which is sending emails following certain rules. With my test want to check if the correct mails are sent. This task exists for several projects, so I really want to find a solution for this.

The method, which collects the receiver of the mails is currently private and you should not expose your privates.

One idea was to write a log entry for each potential mail receiver. But how can I check for certain values in my test.log file?

I found a package, which extends phpunit just for this purpose ( https://github.com/phoenixrvd/phpunit-assert-log-entry ), but it doesn’t look like it is maintained.

Do you know a solution for either

  • check the logs for messages or
  • have a different method to test if the right receivers are notified?

Currently I use a Trait to add logging functionality to my service, which has a setter method, which sets the Logger via the @required annotation.

namespace AppLogger;

use PsrLogLoggerInterface;

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

Advertisement

Answer

One of the big advantages of dependency injection – passing dependencies in, rather than creating them on demand – is that you can replace them with test doubles – Stubs, Mocks, and Spies. PHPUnit supports test doubles out of the box with everything you need.

To assert that a particular line is logged, you need to:

  • Create a mock implementation of LoggerInterface
  • Tell the mock that it should expect a particular call to the info method
  • Set the mock as the logger for an instance of the class you want to test

For example:

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

Asserting that a particular e-mail is sent would be done similarly: the class that actually sends the e-mails should be a dependency of the class that decides which e-mails to send. So you might have something like this:

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');
}
User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement