Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony 5.2 - How to test a class with a dependency on the cache interface

I'm using a Symfony 5.2 installation installed as a skeleton project and running on PHP 7.4:latest

I am using Symfony\Contracts\Cache\TagAwareCacheInterface as an auto-wired constructor injected dependency in a class.

I want to cache the open id response I fetch from the configuration endpoint:

$config = $this->cachePool->get(
    'openid-config',
    function (ItemInterface $item) {
        $item->expiresAfter(self::CACHE_TIME_SECONDS);
        $webContent = file_get_contents($this->openIdConfigurationUrl);
        if (false === $webContent) {
            $message = "Failed to fetch openid config from web at [{$this->openIdConfigurationUrl}]";
            $this->logger->error($message);
            throw new CannotReadOpenIdConfigException($message);
        }
        return $webContent;
    }
);

I want to unit test my class.

The Symfony documentation does not provide an alternative to the callable syntax above.

I have tried to create a mock of the cache using a mock builder:

return $this->getMockBuilder('Symfony\Contracts\Cache\TagAwareCacheInterface')
            ->disableOriginalConstructor()
            ->getMock();

I can't find a way to get this to execute the callable in my object under test.

So, I tried using an actual liveCacheClient instead of mocking this dependency. I could control its contents by adding or removing keys in my test code (icky, but I can't get mocking working)

$liveCacheClient = (self::$container)->get(TagAwareCacheInterface::class);
$liveCacheClient->clear();

If I try to use the container to fetch a real instance of the cache then I get the error that Symfony has removed the class from the container:

Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException: The "Symfony\Contracts\Cache\TagAwareCacheInterface" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.

So, what is the correct recipe to properly unit test code in Symfony cache?

like image 850
Andy Avatar asked Sep 14 '25 13:09

Andy


1 Answers

What about isolating the callable from the unit under test?

$config = $this->cachePool->get('openid-config', [$this->webScraper, 'getWebContent']);

That way you can implement a class like so

class WebScraper
{
    public function getWebContent(ItemInterface $item)
    {
        $item->expiresAfter(self::CACHE_TIME_SECONDS);
        $webContent = file_get_contents($this->openIdConfigurationUrl);
        if (false === $webContent) {
            $message = "Failed to fetch openid config from web at [{$this->openIdConfigurationUrl}]";
            $this->logger->error($message);
            throw new CannotReadOpenIdConfigException($message);
        }
        
        return $webContent;
    }
}

Which can then be unit-tested on its own.

This does mean you're left with one problem; you're still unable to test if $this->webScraper->getWebContent is actually called. However it's a big step forward nevertheless.

like image 65
PtrTon Avatar answered Sep 16 '25 03:09

PtrTon