Skip to content

Commit 4b469a1

Browse files
committed
feature #193 [Platform] Add InMemoryPlatform and InMemoryRawResult for testing (RamyHakam)
This PR was squashed before being merged into the main branch. Discussion ---------- [Platform] Add `InMemoryPlatform` and `InMemoryRawResult` for testing | Q | A | ------------- | --- | Bug fix? |no | New feature? | yes <!-- please update src/**/CHANGELOG.md files --> | Docs? | yes <!-- required for new features --> | Issues | none <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead --> | License | MIT This PR adds two test-friendly classes to the Symfony AI Platform component: ### InMemoryPlatform The `InMemoryPlatform` is a test-specific implementation of `PlatformInterface` that returns a `TextResult` wrapped in a `ResultPromise`. It supports: - A fixed string response (e.g. `'Fake reply'`) - A callable response based on the `Model`, `input`, and `options` This makes it ideal for unit or integration tests, or even functional tests where a full AI stack is not needed. ### InMemoryRawResult The `InMemoryRawResult` implements `RawResultInterface` and is used internally by `InMemoryPlatform`. It wraps: - A structured array (`getData()`) - A generic object (`getObject()`) to simulate what real raw results look like in production usage. ### The Problem While testing new code that uses the AI platform, developers often need to manually create `PlatformInterface` instances. The existing `Platform` and `PlatformFactory` classes are tightly coupled to real AI providers. This in-memory implementation simplifies unit and integration testing by allowing developers to inject a dummy platform and return mock results without any external API calls. ### Example ```php $platform = new InMemoryPlatform('Test reply'); $response = $platform->invoke(new Model('test'), 'Hello'); echo $response->asText(); // "Test reply" Commits ------- 1e9b80e [Platform] Add `InMemoryPlatform` and `InMemoryRawResult` for testing
2 parents c4a7fed + 1e9b80e commit 4b469a1

File tree

5 files changed

+161
-0
lines changed

5 files changed

+161
-0
lines changed

src/platform/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,6 @@ CHANGELOG
5656
* Add exception handling with specific error types
5757
* Add support for embeddings generation across multiple providers
5858
* Add response promises for async operations
59+
* Add InMemoryPlatform and InMemoryRawResult for testing Platform without external Providers calls
60+
61+

src/platform/doc/index.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,30 @@ which can be useful to speed up the processing::
281281
echo $result->asText().PHP_EOL;
282282
}
283283

284+
Testing Tools
285+
-------------
286+
287+
For unit or integration testing, you can use the `InMemoryPlatform`, which implements `PlatformInterface` without calling external APIs.
288+
289+
It supports returning either:
290+
291+
- A fixed string result
292+
- A callable that dynamically returns a response based on the model, input, and options::
293+
294+
use Symfony\AI\Platform\InMemoryPlatform;
295+
use Symfony\AI\Platform\Model;
296+
297+
$platform = new InMemoryPlatform('Fake result');
298+
299+
$result = $platform->invoke(new Model('test'), 'What is the capital of France?');
300+
301+
echo $result->asText(); // "Fake result"
302+
303+
304+
Internally, it uses `InMemoryRawResult` to simulate the behavior of real API responses and support `ResultPromise`.
305+
306+
This allows fast and isolated testing of AI-powered features without relying on live providers or HTTP requests.
307+
284308
.. note::
285309

286310
This requires `cURL` and the `ext-curl` extension to be installed.

src/platform/src/InMemoryPlatform.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\AI\Platform;
13+
14+
use Symfony\AI\Platform\Result\InMemoryRawResult;
15+
use Symfony\AI\Platform\Result\ResultPromise;
16+
use Symfony\AI\Platform\Result\TextResult;
17+
18+
/**
19+
* A fake implementation of PlatformInterface that returns fixed or callable responses.
20+
*
21+
* Useful for unit or integration testing without real API calls.
22+
*
23+
* @author Ramy Hakam <[email protected]>
24+
*/
25+
class InMemoryPlatform implements PlatformInterface
26+
{
27+
/**
28+
* The mock result can be a string or a callable that returns a string.
29+
* If it's a closure, it receives the model, input, and optionally options as parameters like a real platform call.
30+
*/
31+
public function __construct(private readonly \Closure|string $mockResult)
32+
{
33+
}
34+
35+
public function invoke(Model $model, array|string|object $input, array $options = []): ResultPromise
36+
{
37+
$resultText = $this->mockResult instanceof \Closure
38+
? ($this->mockResult)($model, $input, $options)
39+
: $this->mockResult;
40+
41+
$textResult = new TextResult($resultText);
42+
43+
return new ResultPromise(
44+
static fn () => $textResult,
45+
rawResult: new InMemoryRawResult(
46+
['text' => $resultText],
47+
(object) ['text' => $resultText],
48+
),
49+
options: $options
50+
);
51+
}
52+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\AI\Platform\Result;
13+
14+
/**
15+
* A fake implementation of RawResultInterface that returns fixed data.
16+
*
17+
* @author Ramy Hakam <[email protected]>
18+
*/
19+
final readonly class InMemoryRawResult implements RawResultInterface
20+
{
21+
/**
22+
* @param array<string, mixed> $data
23+
*/
24+
public function __construct(
25+
private array $data = [],
26+
private object $object = new \stdClass(),
27+
) {
28+
}
29+
30+
public function getData(): array
31+
{
32+
return $this->data;
33+
}
34+
35+
public function getObject(): object
36+
{
37+
return $this->object;
38+
}
39+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
use PHPUnit\Framework\Attributes\CoversClass;
13+
use PHPUnit\Framework\Attributes\Test;
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\AI\Platform\InMemoryPlatform;
16+
use Symfony\AI\Platform\Model;
17+
18+
#[CoversClass(InMemoryPlatform::class)]
19+
class InMemoryPlatformTest extends TestCase
20+
{
21+
#[Test]
22+
public function platformInvokeWithFixedResponse(): void
23+
{
24+
$platform = new InMemoryPlatform('Mocked result');
25+
$result = $platform->invoke(new Model('test'), 'input');
26+
27+
$this->assertSame('Mocked result', $result->asText());
28+
$this->assertSame('Mocked result', $result->getResult()->getContent());
29+
$this->assertSame(['text' => 'Mocked result'], $result->getRawResult()->getData());
30+
}
31+
32+
#[Test]
33+
public function platformInvokeWithCallableResponse(): void
34+
{
35+
$platform = new InMemoryPlatform(function (Model $model, $input) {
36+
return strtoupper((string) $input);
37+
});
38+
39+
$result = $platform->invoke(new Model('test'), 'dynamic text');
40+
41+
$this->assertSame('DYNAMIC TEXT', $result->asText());
42+
}
43+
}

0 commit comments

Comments
 (0)