Skip to content
Muhammet Şafak edited this page Jun 10, 2026 · 1 revision

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.

Depend on the interface

Inject CacheInterface, never a concrete handler. Production wires a real backend; tests pass a lightweight one.

useInitPHP\Cache\CacheInterface;
finalclass PriceService
{
publicfunction__construct(privateCacheInterface$cache) {}
publicfunctionpriceFor(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;
}
privatefunctionlookupExpensive(string$sku): int { /* ... */ }
}

Option 1 — an in-memory handler (recommended)

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.

usePHPUnit\Framework\TestCase;
useApp\Cache\ArrayHandler; // see the "Custom Handlers" pagefinalclass PriceServiceTest extends TestCase
{
publicfunctiontestCachesTheLookup(): void
{
$cache = newArrayHandler();
$service = newPriceService($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_ABC because the default prefix is cache_. Set ['prefix' => ''] on the handler if you'd rather assert on raw keys.

Option 2 — the File handler in a temp directory

If you'd rather exercise a real backend end-to-end without extra services, point the File handler at a throwaway directory:

usePHPUnit\Framework\TestCase;
useInitPHP\Cache\Cache;
useInitPHP\Cache\Handler\File;
useInitPHP\Cache\CacheInterface;
finalclass PriceServiceFileTest extends TestCase
{
privatestring$dir;
privateCacheInterface$cache;
protectedfunctionsetUp(): void
{
$this->dir = sys_get_temp_dir() . '/cache-test-' . uniqid();
mkdir($this->dir);
$this->cache = Cache::create(File::class, ['path' => $this->dir]);
}
protectedfunctiontearDown(): void
{
array_map('unlink', glob($this->dir . '/*') ?: []);
@rmdir($this->dir);
}
publicfunctiontestRoundTrip(): void
{
$this->cache->set('k', 'v');
self::assertSame('v', $this->cache->get('k'));
}
}

Option 3 — a mock

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);
(newPriceService($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.

Testing expiry without sleep()

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); // deletesself::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).

How the package itself is tested

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.

Next steps

Clone this wiki locally