Skip to content

Commit f9703a8

Browse files
authored
Add testing ability (#27)
1 parent cac7afc commit f9703a8

File tree

6 files changed

+262
-1
lines changed

6 files changed

+262
-1
lines changed

README.md

+43
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,49 @@ echo $result['choices'][0]['text']; // an open-source, widely-used, server-side
5555

5656
For usage examples, take a look at the [openai-php/client](https://github.com/openai-php/client) repository.
5757

58+
## Testing
59+
60+
The `OpenAI` facade comes with a `fake()` method that allows you to fake the API responses.
61+
62+
The fake responses are returned in the order they are provided to the `fake()` method.
63+
64+
All responses are having a `fake()` method that allows you to easily create a response object by only providing the parameters relevant for your test case.
65+
66+
```php
67+
use OpenAI\Laravel\Facades\OpenAI;
68+
use OpenAI\Responses\Completions\CreateResponse;
69+
70+
OpenAI::fake([
71+
CreateResponse::fake([
72+
'choices' => [
73+
[
74+
'text' => 'awesome!',
75+
],
76+
],
77+
]),
78+
]);
79+
80+
$completion = OpenAI::completions()->create([
81+
'model' => 'text-davinci-003',
82+
'prompt' => 'PHP is ',
83+
]);
84+
85+
expect($completion['choices'][0]['text'])->toBe('awesome!');
86+
```
87+
88+
After the requests have been sent there are various methods to ensure that the expected requests were sent:
89+
90+
```php
91+
// assert completion create request was sent
92+
OpenAI::assertSent(Completions::class, function (string $method, array $parameters): bool {
93+
return $method === 'create' &&
94+
$parameters['model'] === 'text-davinci-003' &&
95+
$parameters['prompt'] === 'PHP is ';
96+
});
97+
```
98+
99+
For more testing examples, take a look at the [openai-php/client](https://github.com/openai-php/client#testing) repository.
100+
58101
---
59102

60103
OpenAI PHP for Laravel is an open-sourced software licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"php": "^8.1.0",
1414
"guzzlehttp/guzzle": "^7.5",
1515
"laravel/framework": "^9.46.0|^10.6.2",
16-
"openai-php/client": "^0.4.1"
16+
"openai-php/client": "^0.4.2"
1717
},
1818
"require-dev": {
1919
"laravel/pint": "^1.8",

src/Facades/OpenAI.php

+14
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
namespace OpenAI\Laravel\Facades;
66

77
use Illuminate\Support\Facades\Facade;
8+
use OpenAI\Contracts\ResponseContract;
9+
use OpenAI\Laravel\Testing\OpenAIFake;
10+
use OpenAI\Responses\StreamResponse;
811

912
/**
1013
* @method static \OpenAI\Resources\Audio audio()
@@ -27,4 +30,15 @@ protected static function getFacadeAccessor(): string
2730
{
2831
return 'openai';
2932
}
33+
34+
/**
35+
* @param array<array-key, ResponseContract|StreamResponse|string> $responses
36+
*/
37+
public static function fake(array $responses = []): OpenAIFake /** @phpstan-ignore-line */
38+
{
39+
$fake = new OpenAIFake($responses);
40+
self::swap($fake);
41+
42+
return $fake;
43+
}
3044
}

src/Testing/OpenAIFake.php

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
namespace OpenAI\Laravel\Testing;
4+
5+
use OpenAI\Testing\ClientFake;
6+
7+
class OpenAIFake extends ClientFake
8+
{
9+
}

tests/Arch.php

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
->expect('OpenAI\Laravel\Facades\OpenAI')
99
->toOnlyUse([
1010
'Illuminate\Support\Facades\Facade',
11+
'OpenAI\Contracts\ResponseContract',
12+
'OpenAI\Laravel\Testing\OpenAIFake',
13+
'OpenAI\Responses\StreamResponse',
1114
]);
1215

1316
test('service providers')

tests/Facades/OpenAI.php

+192
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
use OpenAI\Laravel\Facades\OpenAI;
55
use OpenAI\Laravel\ServiceProvider;
66
use OpenAI\Resources\Completions;
7+
use OpenAI\Responses\Completions\CreateResponse;
8+
use PHPUnit\Framework\ExpectationFailedException;
79

810
it('resolves resources', function () {
911
$app = app();
@@ -22,3 +24,193 @@
2224

2325
expect($completions)->toBeInstanceOf(Completions::class);
2426
});
27+
28+
test('fake returns the given response', function () {
29+
OpenAI::fake([
30+
CreateResponse::fake([
31+
'choices' => [
32+
[
33+
'text' => 'awesome!',
34+
],
35+
],
36+
]),
37+
]);
38+
39+
$completion = OpenAI::completions()->create([
40+
'model' => 'text-davinci-003',
41+
'prompt' => 'PHP is ',
42+
]);
43+
44+
expect($completion['choices'][0]['text'])->toBe('awesome!');
45+
});
46+
47+
test('fake throws an exception if there is no more given response', function () {
48+
OpenAI::fake([
49+
CreateResponse::fake(),
50+
]);
51+
52+
OpenAI::completions()->create([
53+
'model' => 'text-davinci-003',
54+
'prompt' => 'PHP is ',
55+
]);
56+
57+
OpenAI::completions()->create([
58+
'model' => 'text-davinci-003',
59+
'prompt' => 'PHP is ',
60+
]);
61+
})->expectExceptionMessage('No fake responses left');
62+
63+
test('append more fake responses', function () {
64+
OpenAI::fake([
65+
CreateResponse::fake([
66+
'id' => 'cmpl-1',
67+
]),
68+
]);
69+
70+
OpenAI::addResponses([
71+
CreateResponse::fake([
72+
'id' => 'cmpl-2',
73+
]),
74+
]);
75+
76+
$completion = OpenAI::completions()->create([
77+
'model' => 'text-davinci-003',
78+
'prompt' => 'PHP is ',
79+
]);
80+
81+
expect($completion)
82+
->id->toBe('cmpl-1');
83+
84+
$completion = OpenAI::completions()->create([
85+
'model' => 'text-davinci-003',
86+
'prompt' => 'PHP is ',
87+
]);
88+
89+
expect($completion)
90+
->id->toBe('cmpl-2');
91+
});
92+
93+
test('fake can assert a request was sent', function () {
94+
OpenAI::fake([
95+
CreateResponse::fake(),
96+
]);
97+
98+
OpenAI::completions()->create([
99+
'model' => 'text-davinci-003',
100+
'prompt' => 'PHP is ',
101+
]);
102+
103+
OpenAI::assertSent(Completions::class, function (string $method, array $parameters): bool {
104+
return $method === 'create' &&
105+
$parameters['model'] === 'text-davinci-003' &&
106+
$parameters['prompt'] === 'PHP is ';
107+
});
108+
});
109+
110+
test('fake throws an exception if a request was not sent', function () {
111+
OpenAI::fake([
112+
CreateResponse::fake(),
113+
]);
114+
115+
OpenAI::assertSent(Completions::class, function (string $method, array $parameters): bool {
116+
return $method === 'create' &&
117+
$parameters['model'] === 'text-davinci-003' &&
118+
$parameters['prompt'] === 'PHP is ';
119+
});
120+
})->expectException(ExpectationFailedException::class);
121+
122+
test('fake can assert a request was sent on the resource', function () {
123+
OpenAI::fake([
124+
CreateResponse::fake(),
125+
]);
126+
127+
OpenAI::completions()->create([
128+
'model' => 'text-davinci-003',
129+
'prompt' => 'PHP is ',
130+
]);
131+
132+
OpenAI::completions()->assertSent(function (string $method, array $parameters): bool {
133+
return $method === 'create' &&
134+
$parameters['model'] === 'text-davinci-003' &&
135+
$parameters['prompt'] === 'PHP is ';
136+
});
137+
});
138+
139+
test('fake can assert a request was sent n times', function () {
140+
OpenAI::fake([
141+
CreateResponse::fake(),
142+
CreateResponse::fake(),
143+
]);
144+
145+
OpenAI::completions()->create([
146+
'model' => 'text-davinci-003',
147+
'prompt' => 'PHP is ',
148+
]);
149+
150+
OpenAI::completions()->create([
151+
'model' => 'text-davinci-003',
152+
'prompt' => 'PHP is ',
153+
]);
154+
155+
OpenAI::assertSent(Completions::class, 2);
156+
});
157+
158+
test('fake throws an exception if a request was not sent n times', function () {
159+
OpenAI::fake([
160+
CreateResponse::fake(),
161+
CreateResponse::fake(),
162+
]);
163+
164+
OpenAI::completions()->create([
165+
'model' => 'text-davinci-003',
166+
'prompt' => 'PHP is ',
167+
]);
168+
169+
OpenAI::assertSent(Completions::class, 2);
170+
})->expectException(ExpectationFailedException::class);
171+
172+
test('fake can assert a request was not sent', function () {
173+
OpenAI::fake();
174+
175+
OpenAI::assertNotSent(Completions::class);
176+
});
177+
178+
test('fake throws an exception if a unexpected request was sent', function () {
179+
OpenAI::fake([
180+
CreateResponse::fake(),
181+
]);
182+
183+
OpenAI::completions()->create([
184+
'model' => 'text-davinci-003',
185+
'prompt' => 'PHP is ',
186+
]);
187+
188+
OpenAI::assertNotSent(Completions::class);
189+
})->expectException(ExpectationFailedException::class);
190+
191+
test('fake can assert a request was not sent on the resource', function () {
192+
OpenAI::fake([
193+
CreateResponse::fake(),
194+
]);
195+
196+
OpenAI::completions()->assertNotSent();
197+
});
198+
199+
test('fake can assert no request was sent', function () {
200+
OpenAI::fake();
201+
202+
OpenAI::assertNothingSent();
203+
});
204+
205+
test('fake throws an exception if any request was sent when non was expected', function () {
206+
OpenAI::fake([
207+
CreateResponse::fake(),
208+
]);
209+
210+
OpenAI::completions()->create([
211+
'model' => 'text-davinci-003',
212+
'prompt' => 'PHP is ',
213+
]);
214+
215+
OpenAI::assertNothingSent();
216+
})->expectException(ExpectationFailedException::class);

0 commit comments

Comments
 (0)