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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With