Time and File System

Testing Time, Randomness and the File System

A Black Friday SaleWindow that calls new DateTimeImmutable() itself can only be tested on Black Friday. Give it a PSR-20 Psr\Clock\ClockInterface (composer require psr/clock), whose single now() method the test controls (symfony/clock ships a ready MockClock):

tests/Unit/SaleWindowTest.php, a clock the test can setPHP
<?php
use PHPUnit\Framework\TestCase;
use Psr\Clock\ClockInterface;
use Shop\SaleWindow;
final class SaleWindowTest extends TestCase {
  public function testTheSaleClosesAtMidnight(): void {
    $clock = new class implements ClockInterface {
      public DateTimeImmutable $now;
      public function now(): DateTimeImmutable { return $this->now; }
    };
    $sale = new SaleWindow($clock, new DateTimeImmutable('2026-11-27'),
      new DateTimeImmutable('2026-11-28'));
    $clock->now = new DateTimeImmutable('2026-11-27 23:59:59');
    $this->assertTrue($sale->isOpen());
    $clock->now = new DateTimeImmutable('2026-11-28 00:00');
    $this->assertFalse($sale->isOpen());
  }
}

Testing both sides of the boundary catches off-by-one bugs. Randomness takes the same seam through PHP 8.2's Random\Randomizer (Subsection 4.7.6): production passes the default secure engine, and a test passes new Randomizer(new Mt19937(42)), which makes the eight-character coupon generator return G6B9V7LM on every run. For files, let the code take a path and give it a fresh one from tempnam(sys_get_temp_dir(), 'shop') or a directory made in setUp() and removed in tearDown(), then check with assertStringEqualsFile().