-
Notifications
You must be signed in to change notification settings - Fork 0
Testing
When your code depends on a cache, you want tests that are fast,
isolated and don't need a running Redis/Memcached. Because everything is
behind CacheInterface, that's easy.
Inject CacheInterface, never a concrete handler. Production wires a real
backend; tests pass a lightweight one.
use InitPHP\Cache\CacheInterface;
final class PriceService
{
public function __construct(private CacheInterface $cache) {}
public function priceFor(string $sku): int
{
if ($this->cache->has("price_$sku")) {
return $this->cache->get("price_$sku");
}
$price = $this->lookupExpensive($sku);
$this->cache->set("price_$sku", $price, 60);
return $price;
}
private function lookupExpensive(string $sku): int { /* ... */ }
}Drop the dependency-free ArrayHandler from Custom Handlers
into your test suite and use it directly. It is real (it goes through
BaseHandler, so keys, TTLs and counters behave
exactly as in production) but holds everything in a PHP array.
use PHPUnit\Framework\TestCase;
use App\Cache\ArrayHandler; // see the "Custom Handlers" page
final class PriceServiceTest extends TestCase
{
public function testCachesTheLookup(): void
{
$cache = new ArrayHandler();
$service = new PriceService($cache);
$first = $service->priceFor('ABC');
$second = $service->priceFor('ABC');
self::assertSame($first, $second);
self::assertTrue($cache->has('cache_price_ABC')); // prefix is applied
}
}The stored key is
cache_price_ABCbecause the defaultprefixiscache_. Set['prefix' => '']on the handler if you'd rather assert on raw keys.
If you'd rather exercise a real backend end-to-end without extra services, point the File handler at a throwaway directory:
use PHPUnit\Framework\TestCase;
use InitPHP\Cache\Cache;
use InitPHP\Cache\Handler\File;
use InitPHP\Cache\CacheInterface;
final class PriceServiceFileTest extends TestCase
{
private string $dir;
private CacheInterface $cache;
protected function setUp(): void
{
$this->dir = sys_get_temp_dir() . '/cache-test-' . uniqid();
mkdir($this->dir);
$this->cache = Cache::create(File::class, ['path' => $this->dir]);
}
protected function tearDown(): void
{
array_map('unlink', glob($this->dir . '/*') ?: []);
@rmdir($this->dir);
}
public function testRoundTrip(): void
{
$this->cache->set('k', 'v');
self::assertSame('v', $this->cache->get('k'));
}
}When you only need to assert that the cache was used, a mock from your test framework is enough:
$cache = $this->createMock(\InitPHP\Cache\CacheInterface::class);
$cache->expects($this->once())->method('set')->with('price_ABC', 42, 60);
$cache->method('has')->willReturn(false);
(new PriceService($cache))->priceFor('ABC');Prefer the in-memory handler for behaviour tests (it actually stores and expires); reach for a mock only to assert specific interactions.
Real-time expiry is awkward to test. Two reliable approaches:
- Assert the zero/negative-TTL = delete behaviour, which is instant:
$cache->set('k', 'v'); $cache->set('k', 'v', 0); // deletes self::assertFalse($cache->has('k'));
- For the File handler, write the underlying file with a past timestamp to force expiry deterministically (this is exactly how the package tests its own File handler).
initphp/cache runs one shared contract test against an in-memory handler,
the File handler and the PDO handler (over SQLite), plus a Redis/Memcached
integration suite gated behind a @group integration marker so the default run
needs no services. It's a good model: test your glue against an in-memory handler
by default, and run the real backend only where it adds value.
-
Custom Handlers — the
ArrayHandlerused above. - Counters / Keys, TTL & Values — the exact behaviour your tests can rely on.
initphp/cache · MIT License · part of the InitPHP family
Source · Issues · Discussions · Packagist · Contributing · Security Policy
Getting Started
Core Concepts
Handlers
Guides
Other