Documentation Index
Fetch the complete documentation index at: https://mintlify.com/phpaisdk/google/llms.txt
Use this file to discover all available pages before exploring further.
The aisdk/google package ships a FakeHttpClient in its test suite that implements the PSR-18 ClientInterface. You inject it into the Sdk instance used by Generate, which means every HTTP call your code makes is intercepted in-memory — no network, no real API key required. You can inspect the exact request body the SDK assembled and assert on the parsed response, giving you full end-to-end coverage of your integration logic.
The FakeHttpClient
FakeHttpClient is a minimal PSR-18 client that returns a preconfigured Response for every request it receives. After each call it stores the original RequestInterface on $lastRequest, and sentBody() decodes the JSON body for easy assertions.
<?php
declare(strict_types=1);
namespace AiSdk\Google\Tests\Fakes;
use Nyholm\Psr7\Response;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
final class FakeHttpClient implements ClientInterface
{
public ?RequestInterface $lastRequest = null;
public function __construct(
private readonly int $status,
private readonly string $body,
private readonly string $contentType = 'application/json',
) {}
public function sendRequest(RequestInterface $request): ResponseInterface
{
$this->lastRequest = $request;
return new Response($this->status, ['Content-Type' => $this->contentType], $this->body);
}
/**
* @return array<string, mixed>
*/
public function sentBody(): array
{
$decoded = json_decode((string) $this->lastRequest?->getBody(), true);
return is_array($decoded) ? $decoded : [];
}
}
Construct it with an HTTP status code and a JSON-encoded string body. You can also override $contentType for non-JSON responses, though all standard Gemini responses use application/json.
Wiring It Up
The helper function configureGoogleWith() used throughout the test suite shows the canonical setup pattern. Create a Psr17Factory (from nyholm/psr7), pass it alongside your FakeHttpClient into a new Sdk instance, configure Generate with that Sdk, and finally call Google::create() with a dummy API key:
use AiSdk\Generate;
use AiSdk\Google;
use AiSdk\Google\Tests\Fakes\FakeHttpClient;
use AiSdk\Support\Sdk;
use Nyholm\Psr7\Factory\Psr17Factory;
function configureGoogleWith(FakeHttpClient $client): void
{
$factory = new Psr17Factory;
Generate::configure(new Sdk(
httpClient: $client,
requestFactory: $factory,
streamFactory: $factory,
));
}
Call this in your test before running any generation. After configuring the Sdk, create the Google provider — this is where the API key header is set, so it needs to happen after Generate::configure():
configureGoogleWith($client);
Google::create(['apiKey' => 'test-key']);
Writing a Text Generation Test
The example below is a complete Pest test that verifies a text generation round-trip. The fake response payload mirrors what the Google Gemini endpoint returns; the test asserts on both the parsed $result and on the raw body the SDK sent.
use AiSdk\Generate;
use AiSdk\Google;
use AiSdk\Google\Tests\Fakes\FakeHttpClient;
it('generates text end to end through the Google vertical', function () {
$client = new FakeHttpClient(200, json_encode([
'id' => 'interaction_google',
'model' => 'gemini-3.5-flash',
'output_text' => 'Hello from Gemini',
'finish_reason' => 'stop',
'usage_metadata' => ['input_tokens' => 6, 'output_tokens' => 3, 'total_tokens' => 9],
]));
configureGoogleWith($client);
Google::create(['apiKey' => 'gemini-test']);
$result = Generate::text('Hi')->model(Google::model('gemini-3.5-flash'))->run();
expect($result->text)->toBe('Hello from Gemini')
->and($result->usage->inputTokens)->toBe(6)
->and($result->providerMetadata['google']['id'])->toBe('interaction_google')
->and($result->providerMetadata['google']['model'])->toBe('gemini-3.5-flash');
$body = $client->sentBody();
expect($body['model'])->toBe('gemini-3.5-flash')
->and($body['input'])->toBe('Hi')
->and($body['generation_config']['max_output_tokens'])->toBe(1024);
expect($client->lastRequest->getUri()->getPath())->toBe('/v1beta/interactions')
->and($client->lastRequest->getHeaderLine('x-goog-api-key'))->toBe('gemini-test');
});
Key assertions to note:
$result->text holds the parsed output text.
$result->usage->inputTokens reflects the token count from usage_metadata.
$result->providerMetadata['google'] gives you the raw provider fields (id, model, etc.).
$client->sentBody() decodes the JSON body so you can assert on every field the SDK serialised.
$client->lastRequest gives you the full PSR-7 request to check the URL path and headers.
Testing Image Generation
For image generation tests, the fake response payload must include candidates[0].content.parts with an inlineData object. The GoogleImageResponseParser extracts the base64 image data from there.
use AiSdk\Generate;
use AiSdk\Google;
use AiSdk\Google\Tests\Fakes\FakeHttpClient;
it('generates images through the Google vertical', function () {
$client = new FakeHttpClient(200, json_encode([
'model' => 'gemini-3.1-flash-image',
'candidates' => [[
'content' => [
'parts' => [
['text' => 'Generated image'],
['inlineData' => ['mimeType' => 'image/png', 'data' => base64_encode('png-bytes')]],
],
],
]],
'usageMetadata' => ['promptTokenCount' => 5, 'candidatesTokenCount' => 7, 'totalTokenCount' => 12],
]));
configureGoogleWith($client);
Google::create(['apiKey' => 'gemini-test']);
$result = Generate::image()
->model(Google::image('gemini-3.1-flash-image'))
->prompt('A tiny banana spaceship')
->aspectRatio('16:9')
->size('2048x2048')
->run();
expect($result->output->base64)->toBe(base64_encode('png-bytes'))
->and($result->output->mimeType)->toBe('image/png')
->and($result->usage->inputTokens)->toBe(5)
->and($result->usage->outputTokens)->toBe(7);
$body = $client->sentBody();
expect($body['contents'][0]['parts'][0]['text'])->toBe('A tiny banana spaceship')
->and($body['generation_config']['response_modalities'])->toBe(['TEXT', 'IMAGE'])
->and($body['generation_config']['image_config']['aspect_ratio'])->toBe('16:9')
->and($body['generation_config']['image_config']['image_size'])->toBe('2K');
expect($client->lastRequest->getUri()->getPath())
->toBe('/v1beta/models/gemini-3.1-flash-image:generateContent')
->and($client->lastRequest->getHeaderLine('x-goog-api-key'))->toBe('gemini-test');
});
Notice that ->size('2048x2048') is serialised as '2K' in the request body — the SDK normalises size strings before sending them to the API.
Resetting State Between Tests
Generate and Google both hold static singleton state. If you do not reset them, a configured provider from one test will bleed into the next. Always call both resets in an afterEach hook:
afterEach(function () {
Generate::reset();
Google::reset();
});
Place this at the top of every test file (or in a shared Pest.php configuration) that configures the Google provider. Missing this step is the most common source of mysterious test-order failures.
Run the test suite with composer test (Pest only) or composer test:all to also run PHPStan static analysis (phpstan analyze) and Laravel Pint linting (pint --test) in one command.composer test
# or
composer test:all