A script that reads $_GET and calls header() and echo needs a server to observe. Write the handler as a class that takes the query as an array and returns a small Response (status, headers, body), and let the front controller do the I/O: new ProductEndpoint($repo)->handle($_GET)->send(). Tests call handle() with plain arrays, and expectOutputString() checks what send() prints through PHPUnit 79,198 's output buffering:
<?php
use PHPUnit\Framework\Attributes\TestWith;
use PHPUnit\Framework\TestCase;
use Shop\{Money, Product, ProductEndpoint};
use Shop\Tests\Fakes\InMemoryProducts;
final class ProductEndpointTest extends TestCase {
private function endpoint(): ProductEndpoint {
$mug = new Product('AC-MUG-01', 'Semicolon Coffee Mug', new Money(1250), 60);
return new ProductEndpoint(new InMemoryProducts($mug));
}
#[TestWith([['sku' => 'AC-MUG-01'], 200])]
#[TestWith([[], 400])]
#[TestWith([['sku' => 'NO-SUCH'], 404])]
public function testStatusCodes(array $query, int $status): void {
$this->assertSame($status, $this->endpoint()->handle($query)->status);
}
public function testSendPrintsTheJsonBody(): void {
$this->expectOutputString('{"error":"no product X"}');
$this->endpoint()->handle(['sku' => 'X'])->send();
}
}All four tests pass. Keep a few real HTTP checks against a running server (php -S, then curl 3,008 -i); in Laravel 2,157 , $this->get('/products/AC-MUG-01') does both at once (Testing and Deployment).