You do not need Cloudflare credentials to test your own code. Use the fakes in this page. The Http::fake example needs a fake token and account ID in config.
The examples use Pest. For PHPUnit, put the same calls in a test method and use $this->assertSame(...) in place of expect(...)->toBe(...).
Fake an agent
SupportAgent::fake(['Reset it from the login page.']);
$response = (new SupportAgent)->prompt('How do I reset my password?');
expect($response->text)->toBe('Reset it from the login page.');
SupportAgent::assertPrompted('How do I reset my password?');With no list of responses, the fake returns Fake response for prompt: followed by the first 10 words of the prompt. You can pass a closure that receives the prompt.
The fake replaces the gateway, so no request reaches the package. Use it to test your own logic.
Fake embeddings
use Laravel\Ai\Embeddings;
Embeddings::fake();
// run your code, then:
Embeddings::assertGenerated(fn ($prompt) => $prompt->inputs === ['hello']);Fake the HTTP layer
To run the real package code, fake the HTTP call. This tests the request body and the response handling.
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
config([
'ai.providers.workers-ai' => [
'driver' => 'workers-ai',
'key' => 'test-key',
'account_id' => 'test-account',
],
]);
Http::fake([
'api.cloudflare.com/*' => Http::response([
'id' => 'chatcmpl-1',
'object' => 'chat.completion',
'model' => '@cf/meta/llama-3.3-70b-instruct-fp8-fast',
'choices' => [[
'index' => 0,
'message' => ['role' => 'assistant', 'content' => 'Hello'],
'finish_reason' => 'stop',
]],
'usage' => ['prompt_tokens' => 10, 'completion_tokens' => 2, 'neurons' => 1.5],
]),
]);
$response = agent('You are a helpful assistant.')->prompt('Hi', provider: 'workers-ai');
Http::assertSent(fn (Request $request) => str_ends_with($request->url(), '/chat/completions'));If your test config sets gateway, use the pattern gateway.ai.cloudflare.com/*.
Fake images, speech, transcription and reranking
Laravel AI’s fakes work on this provider, and send no request. See Image generation, Speech and transcription and Reranking.
use Laravel\Ai\Audio;
use Laravel\Ai\Image;
use Laravel\Ai\Reranking;
use Laravel\Ai\Transcription;
Image::fake();
Audio::fake();
Transcription::fake(['Hello']);
Reranking::fake();Test the neuron event
use Illuminate\Support\Facades\Event;
use Meirdick\WorkersAi\Events\WorkersAiUsageReported;
Event::fake([WorkersAiUsageReported::class]);
// Http::fake(...) as above, then call the agent.
Event::assertDispatched(WorkersAiUsageReported::class);Fake Clef
Clef uses the Laravel AI classification API. Call Classification::fake() with one array per expected call. Each array maps a question key to an answer object.
use Laravel\Ai\Classification;
use Laravel\Ai\Classification\Boolean;
use Laravel\Ai\Prompts\ClassificationPrompt;
use Laravel\Ai\Responses\Data\BooleanAnswer;
Classification::fake([
['decision' => new BooleanAnswer(0.92)],
]);
$response = Classification::of('Checkout is down.')
->question('decision', new Boolean('Is this urgent?'))
->classify(provider: 'workers-ai');
expect($response['decision']->isTrue())->toBeTrue();
Classification::assertClassified(fn (ClassificationPrompt $prompt) => $prompt->contains('Checkout') && $prompt->asks('decision'));A question with no answer in the fake gets a random answer of the right shape. Give an answer for every question your test reads. assertNotClassified() and assertNothingClassified() also exist. Str::decide() asks one question with the key decision, so the same fake works for it.
Clef needs laravel/ai 1.0 or later. The fake sends no request.
Fake Clairic
laravel/ai has no extraction API, so the package adds Meirdick\WorkersAi\Extraction::fake(). Give one array per expected call. Each array maps a field name to a value.
use Meirdick\WorkersAi\Extraction;
use Meirdick\WorkersAi\Extraction\Number;
use Meirdick\WorkersAi\Extraction\Text;
use Meirdick\WorkersAi\Prompts\ExtractionPrompt;
Extraction::fake([
['total' => 1080],
]);
$invoice = Extraction::of('An invoice for Dana.')
->fields(['total' => new Number, 'po' => new Text])
->extract();
expect($invoice['total']->value())->toBe(1080)
->and($invoice['po']->value())->toBeNull()
->and($invoice['po']->pNull())->toBe(1.0);
Extraction::assertExtracted(fn (ExtractionPrompt $prompt) => $prompt->contains('Dana') && $prompt->asks('total'));A field with no value in the fake is null with pNull() of 1.0, the same as a real answer when the text does not determine the field. A call past the end of your list also returns null for every field.
You can pass a closure that receives the ExtractionPrompt and returns the map. Use Extraction::isFaked() to check the state. The assertions are assertExtracted(), assertNotExtracted() and assertNothingExtracted(). The fake sends no request.
Test the extraction events
Each Clairic call dispatches Extracting, then Extracted or ExtractionFailed. The events fire with Extraction::fake() too, because the fake replaces only the HTTP call. Use Event::fake() to assert them. See Exceptions and events.
Live tests
The package repository has live tests that call Cloudflare. They are skipped unless you set WORKERS_AI_E2E_TOKEN and WORKERS_AI_E2E_ACCOUNT. Set WORKERS_AI_E2E_GATEWAY for gateway tests, WORKERS_AI_E2E_WEB_SEARCH=1 for web search, WORKERS_AI_E2E_ARTIFACTS to keep generated files, and WORKERS_AI_E2E_STRESS=1 for the stress sweep. You do not need them to use the package.