Add test fake
gehrisandro opened this issue · comments
Hi @nunomaduro
While using the package I ran into the problem that testing / mocking is too complicated and I tried to make it more Laravel like. So I tinkered a bit around and came up with a solution inspired by Laravels Mail::fake()
and Http::fake()
capabilities.
As the code is still a terrible mess I didn't submit a PR yet instead here is a test snippet which should explain how it works:
public function test_open_ai_fake(): void
{
// switch to fake mode and pass the fake response
OpenAI::fake([
CreateResponse::from([
'choices' => [
[
'text' => 'awesome!',
'index' => 0,
'logprobs' => null,
'finish_reason' => 'length',
],
],
// ... all the other required response params
]),
]);
// execute a request on the api
$response = OpenAI::completions()->create([
'model' => 'text-davinci-003',
'prompt' => 'PHP is ',
]);
// verify the (fake) response
$this->assertEquals('awesome!', $response['choices'][0]['text']);
// do various asserts if the expected request was sent
// all asserts can be done on the facade by passing the resource class ...
OpenAI::assertSent(Completions::class, function ($method, $parameters) {
return $method === 'create' &&
$parameters['model'] === 'text-davinci-003' &&
$parameters['prompt'] === 'PHP is ';
});
// ... or directly on the (faked) resource
OpenAI::completions()->assertSent(function ($method, $parameters) {
return $method === 'create' &&
$parameters['model'] === 'text-davinci-003' &&
$parameters['prompt'] === 'PHP is ';
});
OpenAI::assertSent(Completions::class);
OpenAI::completions()->assertSent();
OpenAI::assertSent(Completions::class, 1);
OpenAI::completions()->assertSent(1);
OpenAI::assertNotSent(Completions::class, function ($method, $parameters) {
return $parameters['prompt'] === 'Python is ';
});
OpenAI::completions()->assertNotSent(function ($method, $parameters) {
return $parameters['prompt'] === 'Python is ';
});
OpenAI::assertNotSent(Chat::class);
OpenAI::chat()->assertNotSent();
OpenAI::assertNothingSent(); // this one does fail because something was sent
}
Do you think this is worth to continue?
At least in my current project it already helped me a lot 😉
Yes. You can move forward.
@gehrisandro happy to help with docs if needed
any progress on this? As a work around I did this
Made a wrapper call just for completions
<?php
namespace App\OpenAi;
use OpenAI\Laravel\Facades\OpenAI;
class CompletionsClientWrapper
{
public function generate($prompt): string
{
if (config('openai.mock')) {
$data = get_fixture('chapter_response.json');
return data_get($data, 'choices.0.text');
}
$result = OpenAI::completions()->create([
'model' => 'text-davinci-003',
'prompt' => $prompt,
'max_tokens' => 2000,
'temperature' => 0,
]);
$context = data_get($result, 'choices.0.text');
return $context;
}
}
Then the json file is in tests/fixtures
{
"id": "cmpl-71xqlCN3VKaEP1xAB18fSrHnrXpAX",
"object": "text_completion",
"created": 1680702747,
"model": "text-davinci-003",
"choices": [
{
"text": "\n\nSome text here",
"index": 0,
"logprobs": null,
"finishReason": "stop"
}
],
"usage": {
"promptTokens": 57,
"completionTokens": 269,
"totalTokens": 326
}
}
Then the .env I did this
OPENAI_MOCK=true
This allowed me to work on the UI without hitting the API and in my tests I did this
\Facades\App\OpenAi\CompletionsClientWrapper::shouldReceive('generate')->andReturn('Some content here');
Taking advantage of Laravel's realtime facades.
The controller uses it too like that
$context = \Facades\App\OpenAi\CompletionsClientWrapper::generate($prompt);