Testing
Testing
This is what's in the test suite, and how to run it. It's for working on the Agent itself. If you're just self-hosting, you can happily skip this whole page.
Overview
The tests are split into four groups:
- Backend Tests: The Foundry VTT callback API
- Frontend Tests: Logins, API connections, and the Laravel framework bits
- Unit Tests: Individual components and helper methods
- Integration Tests: Whole workflows, start to finish
Test Suites
Backend Suite (tests/Feature/Api/)
Everything where the Agent and Foundry VTT talk to each other, and how callbacks get handled.
FoundryCallbackTest.php
The /api/foundry/callback endpoint. Covers:
- Authentication requirements
- Character linking (player and GM-initiated)
- Actor data updates (full and delta)
- Roll results (attack, damage, death saves, initiative, etc.)
- Item management (creation, updates, deletion)
- Cyberdeck and program operations
- Broadcasting and caching
- Request validation
Key Test Cases:
it_requires_authentication()- API is locked downit_handles_link_player_character_request()- Player character linkingit_handles_gm_link_player_character_request()- GM-initiated linkingit_handles_actor_data_update()- Full character sheet syncit_handles_sheet_delta_updates()- Incremental updatesit_handles_roll_result_callback()- Dice roll resultsit_handles_item_creation_delta()- Adding items to inventoryit_handles_item_deletion_delta()- Removing itemsit_handles_item_update_delta()- Modifying item propertiesit_caches_response_for_request_id()- Response cachingit_broadcasts_with_channel_id()- Channel-based broadcasting
FoundryIntegrationTest.php
Plays out real Foundry VTT scenarios from start to finish:
- Complete character linking workflow
- Combat sequences (initiative, attack, damage, ammo tracking)
- Netrunning workflows (program installation, rezzing, attacks)
- Death save sequences
- GM character updates
- Multi-player scenarios
- Item equipping
- Face down rolls
- Stat and skill rolls
Key Test Cases:
complete_character_link_workflow()- Full character setupcomplete_combat_workflow()- Initiative → Attack → Damage → Ammocomplete_netrunning_workflow()- Install → Rez → Attackcomplete_death_save_workflow()- Death save trackinggm_can_update_player_character()- GM permissionsmultiple_players_in_same_channel()- Multi-user support
Frontend Suite (tests/Feature/Frontend/)
Logging in, and the frontend talking to the backend.
AuthenticationFlowTest.php
The Laravel Breeze auth flows:
- Registration and login flows
- Password management
- Profile updates
- Account deletion
- User model attributes (agent_link, channel_id)
- Dashboard access control
Key Test Cases:
new_users_can_register()- User registrationusers_can_authenticate_using_the_login_screen()- Loginusers_can_logout()- Logoutuser_has_agent_link_on_creation()- Auto-generated agent linkuser_has_channel_id_on_creation()- Auto-generated channel IDuser_can_update_profile_information()- Profile editinguser_can_update_password()- Password changes
ApiConnectionTest.php
The frontend talking to the backend API:
- Sanctum token authentication
- API endpoint accessibility
- Error handling and validation
- Large data handling
- Concurrent requests
- Special character handling
- CORS and headers
Key Test Cases:
frontend_can_authenticate_with_sanctum_token()- Token authfrontend_can_send_foundry_callback()- API callsfrontend_receives_validation_errors()- Error responsesfrontend_cannot_access_api_without_authentication()- Securityfrontend_can_handle_large_character_data()- Performancefrontend_can_send_concurrent_requests()- Concurrency
Broadcasting Tests (tests/Feature/BroadcastingTest.php)
Laravel Reverb and broadcasting:
- Event dispatching
- Channel routing
- Authentication
- Payload integrity
- Multi-event handling
Key Test Cases:
foundry_response_event_can_be_dispatched()- Event systemfoundry_response_broadcasts_on_correct_channel()- Channel routingbroadcasting_auth_requires_authentication()- Securityevent_payload_includes_all_required_fields()- Data integrity
Unit Tests (tests/Unit/)
FoundryCallbackHelperTest.php
The arraySetDot() helper, which does all the delta heavy lifting:
- Dot notation path parsing
- Nested updates
- Item array operations (add, update, delete)
- Null value handling (deletion)
- Deep nesting
- Data type handling (numeric, boolean, array)
Key Test Cases:
it_can_parse_dot_notation_paths()- Path parsingit_can_add_items_to_array()- Item creationit_can_update_existing_item_by_id()- Item updatesit_can_delete_item_by_id()- Item deletionit_prevents_duplicate_items_when_adding()- Duplicate prevention
UserModelTest.php
The User model:
- Agent link generation
- Channel ID generation
- Character data storage
- Sanctum token management
- Model attributes and casting
- Fillable fields
Key Test Cases:
user_generates_agent_link_on_creation()- UUID generationuser_generates_channel_id_on_creation()- Channel ID formatuser_character_data_is_cast_to_array()- Data castinguser_can_create_sanctum_tokens()- Token creationgenerate_channel_id_creates_valid_format()- ID validation
Running Tests
Run All Tests
php artisan test
Run Specific Test Suites
# Backend tests only
php artisan test --testsuite=Backend
# Frontend tests only
php artisan test --testsuite=Frontend
# Unit tests only
php artisan test --testsuite=Unit
# Feature tests only
php artisan test --testsuite=Feature
Run Specific Test Files
# Foundry callback tests
php artisan test tests/Feature/Api/FoundryCallbackTest.php
# Integration tests
php artisan test tests/Feature/Api/FoundryIntegrationTest.php
# Authentication tests
php artisan test tests/Feature/Frontend/AuthenticationFlowTest.php
# API connection tests
php artisan test tests/Feature/Frontend/ApiConnectionTest.php
Run Specific Test Methods
php artisan test --filter=it_handles_roll_result_callback
Run with Coverage (requires Xdebug or PCOV)
php artisan test --coverage
Run in Parallel (faster)
php artisan test --parallel
Test Database
Tests run against an in-memory SQLite database, set in phpunit.xml:
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
So the tests are quick, they never touch your dev database, and every run starts from clean.
Continuous Integration
The suite runs fine in a CI/CD pipeline. Here's an example GitHub Actions workflow:
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
- name: Install Dependencies
run: composer install
- name: Run Tests
run: php artisan test
What I'm Aiming For
- Backend API: 100% coverage of Foundry callback types
- Frontend Auth: All Laravel Breeze flows
- Integration: All major user workflows
- Unit: Critical helper methods and models
Writing New Tests
If you're adding tests, here's the rough pattern.
Backend Tests
New Foundry callback type?
- Add test to
FoundryCallbackTest.phpfor the specific callback - Add integration test to
FoundryIntegrationTest.phpfor the workflow - Make sure the event broadcasting is tested too
Frontend Tests
New frontend feature?
- Add authentication tests if auth-related
- Add API connection tests if calling backend
- Test both success and error cases
Unit Tests
New helper or model method?
- Test all code paths
- Test edge cases (null, empty, invalid data)
- Test data type handling
Common Test Patterns
Testing API Endpoints
$response = $this->withToken($this->token)
->postJson('/api/foundry/callback', [
'type' => 'ROLL_RESULT',
'total' => 15,
]);
$response->assertStatus(200);
Testing Events
Event::fake([FoundryResponse::class]);
// ... trigger event ...
Event::assertDispatched(FoundryResponse::class, function ($event) {
return $event->payload['type'] === 'ROLL_RESULT';
});
Testing Database Changes
$this->user->refresh();
$this->assertEquals(40, $this->user->character_data['system']['hp']['value']);
Testing Authentication
$this->actingAs($user)->get('/dashboard')->assertStatus(200);
Troubleshooting
Tests Failing Due to Database
- Make sure your migrations are up to date
- Check
phpunit.xmldatabase configuration - Clear cache:
php artisan config:clear
Event Broadcasting Tests Failing
- Make sure
Event::fake()is called before you trigger the event - Check event class imports
Authentication Tests Failing
- Verify Laravel Breeze is installed
- Check routes are registered
- Check the middleware is configured