Browser Testing

phpspec includes a lightweight HTTP client for testing web endpoints. Make requests and assert on responses — no external dependencies needed.

Configuration

Add base_url to your config:

# phpspec.yaml
base_url: http://localhost:8080

Quick example

describe('User API', function () {
    it('returns a user by id', function () {
        $response = get('/api/users/1');

        expect($response)->toBeOk();
        expect($response)->toHaveStatus(200);
        expect($response->json['name'])->toBe('Chuck Norris');
        expect($response)->toHavePath('data.user.name', 'Chuck Norris');
    });

    it('creates a user', function () {
        $response = post('/api/users', json: ['name' => 'Chuck']);
        expect($response)->toHaveStatus(201);
    });
});

Making requests

FunctionDescription
visit(string $path)GET request (alias for get())
get(string $path)GET request
post(string $path, ...)POST request
put(string $path, ...)PUT request
patch(string $path, ...)PATCH request
delete(string $path)DELETE request

Request options

KeyTypeDescription
jsonarrayJSON-encoded body; sets Content-Type: application/json
bodystringRaw request body
headersarrayAdditional HTTP headers

Response object

PropertyTypeDescription
$response->statusintHTTP status code
$response->bodystringRaw response body
$response->jsonarrayBody decoded as JSON (lazy)
$response->headersarrayResponse headers

Response matchers

toBeOk()

Asserts the response status is 200.

expect($response)->toBeOk();

toBeBad()

Asserts the response status is 400.

toHaveStatus(int $code)

Asserts exact status code.

expect($response)->toHaveStatus(201);
expect($response)->toHaveStatus(404);

toHavePath(string $path, mixed $expected)

Asserts a value at a dot-notation path in the JSON body.

expect($response)->toHavePath('data.user.name', 'Alice');
expect($response)->toHavePath('meta.total', 42);

toHaveHeader(string $name, ?string $value)

Asserts a header exists, optionally checking its value.

expect($response)->toHaveHeader('Content-Type');
expect($response)->toHaveHeader('Content-Type', 'application/json');
expect($response)->not()->toHaveHeader('X-Debug');

toRedirectTo(string $url)

Asserts a 3xx redirect with matching Location header.

expect($response)->toRedirectTo('/dashboard');

Using existing matchers

You don’t need special matchers for everything — use the ones you already know:

// Body
expect($response->body)->toContain('hello');
expect($response->body)->toMatch('/id":\s*\d+/');

// JSON
expect($response->json)->toHaveKey('name');
expect($response->json['items'])->toHaveCount(3);

// Direct value access
expect($response->json['name'])->toBe('Chuck Norris');
expect($response->status)->toBeGreaterThan(199);