> ## Documentation Index
> Fetch the complete documentation index at: https://deck.promptphp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

> Testing strategies and examples for prompts, commands, tracking, and AI SDK integration.

## Introduction

Deck by PromptPHP is designed to be easily testable. This guide covers strategies for testing prompts, commands, tracking, and AI SDK integration in your Laravel application.

## Setting up tests

### Test base class

If you're using Orchestra Testbench (recommended for package testing), register the service provider in your test case:

```php theme={null}
<?php

namespace Tests;

use Orchestra\Testbench\TestCase as BaseTestCase;
use PromptPHP\Deck\Providers\DeckServiceProvider;

abstract class TestCase extends BaseTestCase
{
    protected function getPackageProviders($app): array
    {
        return [
            DeckServiceProvider::class,
        ];
    }

    protected function getPackageAliases($app): array
    {
        return [
            'Deck' => \PromptPHP\Deck\Facades\Deck::class,
        ];
    }
}
```

### Disabling cache and tracking

For most tests, disable caching and tracking to avoid side effects:

```php theme={null}
protected function defineEnvironment($app): void
{
    $app['config']->set('deck.cache.enabled', false);
    $app['config']->set('deck.tracking.enabled', false);
    $app['config']->set('deck.path', $this->getFixturePath('prompts'));
}

protected function getFixturePath(string $path = ''): string
{
    return __DIR__ . '/fixtures/' . $path;
}
```

## Testing prompts

### Creating test prompt files

Create fixture prompt files in your test directory:

```
tests/
└── fixtures/
    └── prompts/
        └── order-summary/
            ├── v1/
            │   ├── system.md
            │   ├── user.md
            │   └── metadata.json
            └── metadata.json
```

`tests/fixtures/prompts/order-summary/v1/system.md`:

```markdown theme={null}
You are a {{ $tone }} AI assistant specialized in order summaries.
```

`tests/fixtures/prompts/order-summary/v1/user.md`:

```markdown theme={null}
Summarise this order: {{ $input }}
```

### Testing prompt loading

```php theme={null}
use PromptPHP\Deck\Facades\Deck;

it('loads a prompt by name', function () {
    $prompt = Deck::get('order-summary', 1);

    expect($prompt->name())->toBe('order-summary');
    expect($prompt->version())->toBe(1);
});

it('throws when prompt does not exist', function () {
    Deck::get('non-existent');
})->throws(\PromptPHP\Deck\Exceptions\PromptNotFoundException::class);

it('throws when version does not exist', function () {
    Deck::get('order-summary', 999);
})->throws(\PromptPHP\Deck\Exceptions\InvalidVersionException::class);
```

### Testing variable interpolation

```php theme={null}
it('interpolates variables in prompt content', function () {
    $prompt = Deck::get('order-summary', 1);

    $content = $prompt->system(['tone' => 'friendly']);

    expect($content)->toContain('friendly');
    expect($content)->not->toContain('{{ $tone }}');
});

it('leaves unmatched placeholders intact', function () {
    $prompt = Deck::get('order-summary', 1);

    $content = $prompt->system([]);

    expect($content)->toContain('{{ $tone }}');
});
```

### Testing roles

```php theme={null}
it('lists available roles', function () {
    $prompt = Deck::get('order-summary', 1);

    expect($prompt->roles())->toContain('system', 'user');
});

it('checks if a role exists', function () {
    $prompt = Deck::get('order-summary', 1);

    expect($prompt->has('system'))->toBeTrue();
    expect($prompt->has('nonexistent'))->toBeFalse();
});

it('returns empty string for missing roles', function () {
    $prompt = Deck::get('order-summary', 1);

    expect($prompt->role('nonexistent'))->toBe('');
});

it('gets raw content without interpolation', function () {
    $prompt = Deck::get('order-summary', 1);

    $raw = $prompt->raw('system');

    expect($raw)->toContain('{{ $tone }}');
});
```

### Testing messages output

```php theme={null}
it('builds messages array for AI APIs', function () {
    $prompt = Deck::get('order-summary', 1);

    $messages = $prompt->toMessages(['tone' => 'friendly', 'input' => 'Order #123']);

    expect($messages)->toBeArray();
    expect($messages[0])->toHaveKeys(['role', 'content']);
    expect($messages[0]['role'])->toBe('system');
});

it('filters messages to specific roles', function () {
    $prompt = Deck::get('order-summary', 1);

    $messages = $prompt->toMessages([], ['system']);

    expect($messages)->toHaveCount(1);
    expect($messages[0]['role'])->toBe('system');
});
```

## Testing with the facade

You can mock the facade in tests where you don't want filesystem access:

```php theme={null}
use PromptPHP\Deck\Facades\Deck;
use PromptPHP\Deck\PromptTemplate;

it('uses a mocked prompt', function () {
    Deck::shouldReceive('get')
        ->with('order-summary', null)
        ->andReturn(new PromptTemplate(
            'order-summary',
            1,
            ['system' => 'You are a helpful assistant.'],
            ['description' => 'Test prompt']
        ));

    $prompt = Deck::get('order-summary');

    expect($prompt->system())->toBe('You are a helpful assistant.');
});
```

## Testing version management

```php theme={null}
it('lists all versions for a prompt', function () {
    $versions = Deck::versions('order-summary');

    expect($versions)->toBeArray();
    expect($versions[0])->toHaveKey('version');
});

it('activates a specific version', function () {
    // With tracking disabled, this writes to metadata.json
    $result = Deck::activate('order-summary', 1);

    expect($result)->toBeTrue();
});
```

## Testing execution tracking

When testing tracking, enable it and run the migrations in your test setup:

```php theme={null}
protected function defineEnvironment($app): void
{
    $app['config']->set('deck.tracking.enabled', true);
    $app['config']->set('database.default', 'testing');
}

protected function defineDatabaseMigrations(): void
{
    $this->loadMigrationsFrom(__DIR__ . '/../src/Database/migrations');
}
```

```php theme={null}
use PromptPHP\Deck\Models\PromptExecution;

it('tracks prompt executions', function () {
    Deck::track('order-summary', 1, [
        'input'  => ['message' => 'test'],
        'output' => 'response',
        'tokens' => 100,
    ]);

    expect(PromptExecution::count())->toBe(1);
    expect(PromptExecution::first()->prompt_name)->toBe('order-summary');
    expect(PromptExecution::first()->tokens)->toBe(100);
});

it('does not track when disabled', function () {
    config(['deck.tracking.enabled' => false]);

    Deck::track('order-summary', 1, ['tokens' => 50]);

    expect(PromptExecution::count())->toBe(0);
});
```

### Using factories

Use the included factories to seed test data:

```php theme={null}
use PromptPHP\Deck\Models\PromptVersion;
use PromptPHP\Deck\Models\PromptExecution;

it('queries execution data', function () {
    PromptExecution::factory()
        ->forPrompt('order-summary', 2)
        ->count(10)
        ->create();

    $avg = PromptExecution::where('prompt_name', 'order-summary')
        ->avg('latency_ms');

    expect($avg)->toBeGreaterThan(0);
});

it('finds the active version', function () {
    PromptVersion::factory()->named('order-summary')->version(1)->create();
    PromptVersion::factory()->named('order-summary')->version(2)->active()->create();

    $active = PromptVersion::where('name', 'order-summary')
        ->where('is_active', true)
        ->first();

    expect($active->version)->toBe(2);
});
```

## Testing Artisan commands

```php theme={null}
use Illuminate\Support\Facades\File;

it('creates a prompt via make:prompt', function () {
    $this->artisan('make:prompt', ['name' => 'test-prompt'])
        ->assertSuccessful();

    $promptPath = config('deck.path') . '/test-prompt/v1';
    expect(File::isDirectory($promptPath))->toBeTrue();
    expect(File::exists($promptPath . '/system.md'))->toBeTrue();
});

it('lists prompts via prompt:list', function () {
    // Create a prompt first
    $this->artisan('make:prompt', ['name' => 'list-test']);

    $this->artisan('prompt:list')
        ->assertSuccessful();
});

it('activates a version via prompt:activate', function () {
    $this->artisan('make:prompt', ['name' => 'activate-test']);

    $this->artisan('prompt:activate', [
        'name'    => 'activate-test',
        'version' => 1,
    ])->assertSuccessful();
});

it('tests a prompt via prompt:test', function () {
    $this->artisan('make:prompt', ['name' => 'render-test']);

    $this->artisan('prompt:test', [
        'name' => 'render-test',
    ])->assertSuccessful();
});

it('shows diff between versions', function () {
    $this->artisan('make:prompt', ['name' => 'diff-test']);

    $this->artisan('prompt:diff', [
        'name'  => 'diff-test',
        '--v1'  => 1,
        '--v2'  => 1,  // Compare with self for basic test
    ])->assertSuccessful();
});
```

## Testing AI SDK integration

### Testing HasPromptTemplate

Create a test agent class that uses the trait:

```php theme={null}
use PromptPHP\Deck\Concerns\HasPromptTemplate;

class TestAgent
{
    use HasPromptTemplate;
}
```

```php theme={null}
it('derives prompt name from class name', function () {
    $agent = new TestAgent;

    expect($agent->promptName())->toBe('test-agent');
});

it('returns null for default prompt version', function () {
    $agent = new TestAgent;

    expect($agent->promptVersion())->toBeNull();
});

it('returns empty array for default variables', function () {
    $agent = new TestAgent;

    expect($agent->promptVariables())->toBe([]);
});
```

### Clearing cached templates

When testing with different prompt versions:

```php theme={null}
it('clears cached template between tests', function () {
    $agent = new TestAgent;

    // Load v1
    $template1 = $agent->promptTemplate();

    // Clear cache and load fresh
    $agent->forgetPromptTemplate();
    $template2 = $agent->promptTemplate();

    // Both are fresh instances
    expect($template1)->not->toBe($template2);
});
```

<Tip>
  Always call `forgetPromptTemplate()` in tests where you modify prompt files
  or change the active version between assertions.
</Tip>
