Skip to main content

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

Build docs developers (and LLMs) love