meritum / http-testing
HTTP request/response testing helpers for the Meritum ecosystem
Requires
- php: ^8.4
- laminas/laminas-diactoros: ^3.0
- phpunit/phpunit: ^11.0
- psr/http-message: ^1.1
- psr/http-server-handler: ^1.0
Requires (Dev)
- phpstan/phpstan: ^2.0
- squizlabs/php_codesniffer: ^3.11
README
HTTP request/response testing helpers for the Meritum ecosystem — builds a PSR-7 request, drives it through any PSR-15 RequestHandlerInterface, and wraps the resulting response for assertions. No dependency on meritum/testing or meritum/http — it works against the standard PSR-7/PSR-15 interfaces, so any consumer's app kernel qualifies as long as it implements RequestHandlerInterface. Retrieving that handler from wherever a project stores it (a test kernel, a bare property, etc.) is the consuming project's own responsibility, not this package's.
Requirements
- PHP 8.4+
phpunit/phpunit^11.0psr/http-message^1.1psr/http-server-handler^1.0laminas/laminas-diactoros^3.0
Installation
composer require meritum/http-testing
Usage
HttpTestingTrait
The usual way to use this package: add the trait to a PHPUnit test case and implement getRequestHandler() to return whatever RequestHandlerInterface your app kernel exposes — however you obtain it is up to you:
use PHPUnit\Framework\TestCase; use Psr\Http\Server\RequestHandlerInterface; use Meritum\HttpTesting\HttpTestingTrait; abstract class ApiTestCase extends TestCase { use HttpTestingTrait; protected function getRequestHandler(): RequestHandlerInterface { return $this->kernel->get(HttpKernelInterface::class); // however your project stores it } }
Every test then gets get(), post(), put(), patch(), delete(), head(), and options(), each returning a TestResponse:
final class PostsTest extends ApiTestCase { public function test_it_creates_a_post(): void { $this->post('/posts', ['title' => 'Hello']) ->assertCreated() ->assertAttributeEquals('data.title', 'Hello'); } }
getRequestHandler() is only called once per test — the trait caches the Client it builds around the returned handler, so it's safe (and expected) to resolve the handler from a kernel or container each time without worrying about rebuilding it per request.
Client
The lower-level piece the trait is built on, for anyone not using PHPUnit test cases directly:
use Meritum\HttpTesting\Client; $client = new Client($handler); // any Psr\Http\Server\RequestHandlerInterface $response = $client->request('POST', '/posts', ['title' => 'Hello']);
For POST/PUT/PATCH, the parsed body is JSON-encoded and written directly into the request's body stream (not set as PSR-7's parsedBody) — this matches how most middleware pipelines actually read the request body, rather than relying on parsedBody being populated. Content-Type: application/json is set by default for those methods unless a header already provides one.
TestResponse
Wraps whatever ResponseInterface comes back, with chainable assertions:
assertStatus(int $status)plus shortcuts for the common codes —assertOk,assertCreated,assertAccepted,assertNoContent,assertMovedPermanently,assertFound,assertBadRequest,assertUnauthorized,assertForbidden,assertNotFound,assertMethodNotAllowed,assertNotAcceptable,assertRequestTimeout,assertConflict,assertGone,assertUnsupportedMediaType,assertUnprocessableContent,assertTooManyRequests,assertInternalServerError,assertBadGateway,assertServiceUnavailable,assertGatewayTimeoutassertHeader(string $name, string $value)assertBodyEquals(array $expected)— loose equality against the full decoded JSON bodyassertAttributeExists(string $path)/assertAttributeType(string $path, string $type)/assertAttributeEquals(string $path, mixed $value)— dot-path lookups resolved from the response body root (e.g.'data.name', not assumed to start under any particular key), so these work equally well against a success envelope or an error envelopegetResponse(): ResponseInterface/getResponseBody(): array— escape hatches for anything not covered by the assertion methods above
Every assertion method returns $this, so they chain:
$this->get('/posts/1') ->assertOk() ->assertHeader('Content-Type', 'application/json') ->assertAttributeEquals('data.id', '1');