Nette Tester: HTTP testing has never been so easy

yesterday by David Grudl  

When was the last time you changed your server configuration, modified .htaccess, or rewrote nginx rules? And did you check if this accidentally broke redirects, made robots.txt inaccessible, or exposed a hidden directory to unauthorized users? Do you check this automatically, or do you just manually click through the website hoping everything works fine?

With the new HttpAssert class in Nette Tester 2.5.6, you can automate all these critical checks and never have to worry that a configuration change might break your website.

Imagine automatically verifying after every nginx configuration change:

// Critical paths are still accessible
HttpAssert::fetch('https://example.com/robots.txt')
    ->expectCode(200)
    ->expectHeader('Content-Type', contains: 'text/plain');

// Admin section is properly protected
HttpAssert::fetch('https://example.com/admin')
    ->expectCode(403);

// API endpoint works
HttpAssert::fetch('https://example.com/api/health')
    ->expectCode(200)
    ->expectBody(contains: '"status":"ok"');

All validation methods at your fingertips

HttpAssert allows you to easily perform HTTP requests and verify their status codes, headers, and response body content. The fetch() method returns an HttpAssert instance for subsequent verification using a fluent interface. You can use the same pattern matching you know from Assert::match().

HttpAssert::fetch('https://api.example.com/data')
    // Status codes
    ->expectCode(200)                                   // exact code

    // Headers in every way
    ->expectHeader('Content-Type')                      // header must exist
    ->expectHeader('Content-Type', 'application/json')  // exact value
    ->expectHeader('Content-Type', contains: 'json')    // contains text
    ->expectHeader('Server', matches: 'nginx %a%')      // pattern matching

    // Response body
    ->expectBody('OK')                                  // exact value
    ->expectBody(contains: '"success": true')           // contains text
    ->expectBody(matches: '%A%"users":[%a%]%A%');       // matches pattern

The deny* methods are also available and support the same options as their expect* counterparts:

HttpAssert::fetch('https://api.example.com/data')
    ->denyCode(200)                                   // must not be 200
    ->denyHeader('Content-Type')                      // header must not exist
    ->denyHeader('Content-Type', contains: 'json')    // does not contain text
    ->denyHeader('Server', matches: 'nginx %a%')      // does not match pattern
    ->denyBody('OK')                                  // body must not have value
    ->denyBody(contains: '"success": true')           // does not contain text
    ->denyBody(matches: '~exception|fatal~i');        // does not match pattern

Callback functions for advanced validation

Sometimes you need more than just simple comparisons. That's why HttpAssert supports callback functions in all verification methods. You can write your own validation logic while still using the elegant API:

HttpAssert::fetch('https://api.example.com/data')
    ->expectCode(fn($code) => $code === 200 || $code === 201)
    ->expectHeader('Content-Length', fn($length) => $length > 1000)
    ->expectBody(fn($body) => json_decode($body) !== null);

Intelligent redirect handling

You have full control over redirects – you can follow them or test them separately:

// Test redirect without following
HttpAssert::fetch('https://example.com/old-blog', follow: false)
    ->expectCode(301)
    ->expectHeader('Location', 'https://example.com/blog');

// Follow redirects to the end
HttpAssert::fetch('https://example.com/old-blog', follow: true)
    ->expectCode(200)
    ->expectBody(contains: 'Welcome to our new blog');

Flexible request configuration

Whether you're testing GET, POST, PUT, DELETE, or any other HTTP method, HttpAssert can handle it. And of course you can send a request body, headers, or cookies:

HttpAssert::fetch(
    'https://api.example.com/protected',
    method: 'POST',
    headers: [
        'Authorization' => 'Bearer ' . $token,  // associative array
        'Accept: application/json',              // or as string
    ],
    cookies: ['session' => $sessionId],
    body: json_encode($data)
)
    ->expectCode(201)
    ->expectBody(contains: 'created');

Clear error messages

When a test fails, HttpAssert tells you exactly what went wrong:

Expected HTTP status code 200 but got 404
Header 'Content-Type' should contain 'json' but was 'text/html'
Body should contain 'success'

What else is new in Tester 2.5.6?

Besides HttpAssert, version 2.5.6 also brings support for the upcoming PHP 8.5, which will be released at the end of the year. Nette Tester thus always stays one step ahead and ready for the latest PHP versions.

Try HttpAssert and let us know how you like it!

David Grudl An artificial intelligence and web technology specialist, creator of the Nette Framework and other popular open-source projects. He writes for Uměligence, phpFashion, and La Trine blogs. He conducts AI training workshops and hosts the Tech Guys show. He's passionate about making artificial intelligence accessible through clear, practical explanations. Creative and pragmatic, he has a keen eye for real-world technology applications.