Skip to content

Testing your code against the SDK

The SDK's transport is symfony/http-client. Inject Symfony's MockHttpClient through ClientBuilder::withHttpClient() and you get full control over what each call returns — no network IO, fully deterministic.

Minimal example

use CleverCloud\Sdk\Auth\Credentials;
use CleverCloud\Sdk\ClientBuilder;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;

$mock = new MockHttpClient([
    new MockResponse(
        json_encode(['id' => 'app_42', 'name' => 'hello']),
        ['response_headers' => ['content-type' => 'application/json']],
    ),
]);

$client = (new ClientBuilder())
    ->withCredentials(Credentials::apiToken('test'))
    ->withHttpClient($mock)
    ->build();

$app = $client->applications->get('app_42');
assert($app->name === 'hello');

MockHttpClient queues responses — each request the SDK makes pops the next one. Pass a Closure instead of an array to inspect the URL/method and respond dynamically.

A runnable variant lives at examples/mocking.php.

Inspecting what the SDK sent

After the call, MockResponse exposes the request that produced it:

$response = new MockResponse(
    '{"id": "app_42"}',
    ['response_headers' => ['content-type' => 'application/json']],
);

$client->applications->restart('app_42', 'orga_1', withoutCache: true);

$response->getRequestMethod();   // 'POST'
$response->getRequestUrl();      // 'https://api.clever-cloud.com/v2/organisations/orga_1/applications/app_42/instances?useCache=no'
$response->getRequestOptions();  // ['headers' => [...], 'body' => '{}', ...]

The SDK's own test suite uses this exact pattern — see tests/Unit/Resource/V2/ApplicationsResourceTest.php or any other *ResourceTest.php.

Dynamic response callback

When you need a response that depends on the URL, pass a callable:

$mock = new MockHttpClient(function (string $method, string $url, array $options) {
    if (str_ends_with($url, '/v2/self')) {
        return new MockResponse(json_encode(['id' => 'me_1', 'email' => 'me@example.com']));
    }
    return new MockResponse('{"error": "unexpected URL"}', ['http_code' => 404]);
});

Asserting headers and body

The SDK ships a small helper for header assertions — tests/Unit/Fixture/ResourceFactory::headers() and findHeader(). If you want to use the same pattern in your own tests, replicate this snippet:

function findHeader(MockResponse $r, string $name): ?string {
    foreach ($r->getRequestOptions()['headers'] ?? [] as $line) {
        if (\is_string($line) && str_starts_with($line, $name.':')) {
            return $line;
        }
    }
    return null;
}

// Usage:
$auth = findHeader($response, 'Authorization');
assert(str_starts_with($auth, 'Authorization: Bearer '));

Verified against the helpers in tests/Unit/Fixture/ResourceFactory.php.

Pinning the OAuth1 signer for deterministic signatures

If your tests sign OAuth1 requests and compare them to fixtures:

use Symfony\Component\Clock\MockClock;
use CleverCloud\Sdk\Auth\NonceGenerator;

final class FixedNonce implements NonceGenerator {
    public function __construct(private string $value) {}
    public function generate(): string { return $this->value; }
}

$client = (new ClientBuilder())
    ->withCredentials(Credentials::oauth1('ck', 'cs', 'tk', 'ts'))
    ->withHttpClient($mock)
    ->withClock(new MockClock('@1700000000'))
    ->withNonceGenerator(new FixedNonce('test-nonce'))
    ->build();

Testing the SSE log stream

LogsResource::stream() opens a Symfony SSE connection. MockHttpClient returns SSE frames the same way regular responses work:

$frame1 = json_encode(['message' => 'hello', 'instance_id' => 'i_1']);
$frame2 = json_encode(['message' => 'world', 'instance_id' => 'i_2']);

$response = new MockResponse(
    ['data: '.$frame1."\n\n", 'data: '.$frame2."\n\n"],
    ['response_headers' => ['content-type' => 'text/event-stream']],
);

$client = (new ClientBuilder())
    ->withCredentials(Credentials::apiToken('test'))
    ->withHttpClient(new MockHttpClient([$response]))
    ->build();

$entries = iterator_to_array($client->logs->stream('app_42', 'orga_1'), false);
assert(count($entries) === 2);
assert($entries[0]->message === 'hello');

See tests/Unit/Resource/V4/LogsResourceTest.php.

Asserting an exception is raised

use CleverCloud\Sdk\Exception\ValidationException;

$mock = new MockHttpClient([
    new MockResponse(
        json_encode([
            'message' => 'invalid input',
            'errors' => ['name' => ['must not be blank', 'too short']],
        ]),
        ['http_code' => 422, 'response_headers' => ['content-type' => 'application/json']],
    ),
]);

try {
    $client->applications->create(['name' => '']);
    fail('expected ValidationException');
} catch (ValidationException $e) {
    assert($e->statusCode === 422);
    assert($e->errors['name'] === ['must not be blank', 'too short']);
}