Testing

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 down
  • it_handles_link_player_character_request() - Player character linking
  • it_handles_gm_link_player_character_request() - GM-initiated linking
  • it_handles_actor_data_update() - Full character sheet sync
  • it_handles_sheet_delta_updates() - Incremental updates
  • it_handles_roll_result_callback() - Dice roll results
  • it_handles_item_creation_delta() - Adding items to inventory
  • it_handles_item_deletion_delta() - Removing items
  • it_handles_item_update_delta() - Modifying item properties
  • it_caches_response_for_request_id() - Response caching
  • it_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 setup
  • complete_combat_workflow() - Initiative → Attack → Damage → Ammo
  • complete_netrunning_workflow() - Install → Rez → Attack
  • complete_death_save_workflow() - Death save tracking
  • gm_can_update_player_character() - GM permissions
  • multiple_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 registration
  • users_can_authenticate_using_the_login_screen() - Login
  • users_can_logout() - Logout
  • user_has_agent_link_on_creation() - Auto-generated agent link
  • user_has_channel_id_on_creation() - Auto-generated channel ID
  • user_can_update_profile_information() - Profile editing
  • user_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 auth
  • frontend_can_send_foundry_callback() - API calls
  • frontend_receives_validation_errors() - Error responses
  • frontend_cannot_access_api_without_authentication() - Security
  • frontend_can_handle_large_character_data() - Performance
  • frontend_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 system
  • foundry_response_broadcasts_on_correct_channel() - Channel routing
  • broadcasting_auth_requires_authentication() - Security
  • event_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 parsing
  • it_can_add_items_to_array() - Item creation
  • it_can_update_existing_item_by_id() - Item updates
  • it_can_delete_item_by_id() - Item deletion
  • it_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 generation
  • user_generates_channel_id_on_creation() - Channel ID format
  • user_character_data_is_cast_to_array() - Data casting
  • user_can_create_sanctum_tokens() - Token creation
  • generate_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?

  1. Add test to FoundryCallbackTest.php for the specific callback
  2. Add integration test to FoundryIntegrationTest.php for the workflow
  3. Make sure the event broadcasting is tested too

Frontend Tests

New frontend feature?

  1. Add authentication tests if auth-related
  2. Add API connection tests if calling backend
  3. Test both success and error cases

Unit Tests

New helper or model method?

  1. Test all code paths
  2. Test edge cases (null, empty, invalid data)
  3. 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.xml database 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

Further Reading