Skip to content

Alxarafe Testing Guide ​

Introduction ​

Automated testing is a fundamental pillar in development with Alxarafe. It not only ensures that the code works as expected but also acts as a safety net allowing for aggressive refactoring and serving as living documentation of the system's behavior.

Alxarafe includes a robust PHPUnit configuration integrated with Docker, designed to facilitate both unit tests (models, pure logic) and feature tests (controllers, API).


Testing Environment ​

Infrastructure ​

The testing environment is containerized and relies on the following pillars:

  1. Docker Container (alxarafe_php): All tests run inside the PHP container to ensure consistency with the production environment (same extensions, PHP version 8.5+).
  2. Isolated Database (alxarafe_test):
    • When tests start, the system automatically connects to a dedicated database called alxarafe_test.
    • This database is automatically created and migrated if it doesn't exist.
    • Important: Tests are never run against the development or production database.
  3. Database Transactions:
    • Each test is automatically wrapped in a database transaction (beginTransaction in setUp).
    • Upon test completion, the transaction is rolled back (rollBack in tearDown).
    • This ensures that no data persists after a test execution, keeping the environment clean for the next one.

Bootstrapping ​

The Tests/bootstrap.php file (and its class Tests\Bootstrapper) is responsible for:

  • Defining the ALX_TESTING constant.
  • Loading environment configurations.
  • Initializing the connection to the test database.
  • Executing necessary migrations.

Directory Structure ​

The project distinguishes between "core" (framework) tests and "application" (skeleton) tests.

  • Tests/: Contains the base testing infrastructure and unit tests for the Alxarafe framework itself (src/Core).
    • Tests/TestCase.php: Base class from which all tests must inherit.
    • Tests/Unit/: Core unit tests.
    • Tests/Feature/: Core integration tests.
  • skeleton/Tests/: Contains specific tests for the example application or final project.
    • skeleton/Tests/Unit/: Tests for your Models and Classes.
    • skeleton/Tests/Feature/: Tests for your Controllers and APIs.

Creating Tests ​

All tests must inherit from Tests\TestCase. This base class provides transaction utilities and custom assertions.

1. Model Tests (Unit) ​

Unit tests for models verify that business logic and persistence work correctly.

Example: skeleton/Tests/Unit/PersonTest.php

php
<?php

namespace Tests\Unit;

use Tests\TestCase;
use Modules\Agenda\Model\Person;

class PersonTest extends TestCase
{
    /**
     * Verifies that a person can be created and saved to DB.
     */
    public function testItCanCreateAPerson()
    {
        // 1. Execution
        $person = Person::create([
            'name' => 'John',
            'lastname' => 'Doe',
            'birth_date' => '1990-01-01',
            'active' => true
        ]);

        // 2. Assertions
        $this->assertNotNull($person->id, 'ID should not be null after creation');
        $this->assertEquals('John', $person->name);
        $this->assertTrue($person->active);

        // 3. Database Verification
        // (Note: This checks within the current transaction)
        $this->assertDatabaseHas('people', [
            'id' => $person->id,
            'name' => 'John'
        ]);
    }
}

2. API and Controller Tests (Feature) ​

Testing controllers in Alxarafe has particularities because the framework handles HTTP responses and redirects.

Handling Responses (HttpResponseException) ​

In the test environment (ALX_TESTING), functions like httpRedirect or jsonResponse do not terminate execution (die()). Instead, they throw an Alxarafe\Base\Testing\HttpResponseException. This allows capturing the response and making assertions on it.

Simulating Authentication ​

To test protected controllers without going through the full login process, you can define ALX_TEST_USER before instantiating the controller.

Example: skeleton/Tests/Feature/PersonControllerTest.php

php
<?php

namespace Tests\Feature;

use Tests\TestCase;
use Modules\Agenda\Controller\PersonController;
use Alxarafe\Base\Testing\HttpResponseException;

class PersonControllerTest extends TestCase
{
    /**
     * Expected failure test: Validation.
     */
    public function testItReturnsValidationErrorOnEmptySave()
    {
        // 1. Prepare Environment
        $_POST = ['action' => 'save', 'data' => []]; // Empty data to trigger error
        
        // Simulate authenticated user (if needed)
        if (!defined('ALX_TEST_USER')) define('ALX_TEST_USER', 'Tester');

        $controller = new PersonController();

        try {
            // 2. Execute Protected Method via Reflection
            // (Necessary because saveRecord is protected in ResourceController)
            $reflection = new \ReflectionClass($controller);
            
            // Initialize internal controller configuration
            $configMethod = $reflection->getMethod('buildConfiguration');
            $configMethod->setAccessible(true);
            $configMethod->invoke($controller);

            // Invoke save
            $method = $reflection->getMethod('saveRecord');
            $method->setAccessible(true);
            $method->invoke($controller);

            // If no exception happens, the test fails
            $this->fail("HttpResponseException was expected due to validation");
        } catch (HttpResponseException $e) {
            // 3. Verify Error Response
            $response = $e->getResponse();
            $this->assertArrayHasKey('error', $response);
            $this->assertEquals('No data provided', $response['error']);
        }
    }

    /**
     * Success test: Correct save.
     */
    public function testItCanSaveAPersonViaController()
    {
        // 1. Valid Data
        $_POST = ['data' => [
            'name' => 'Jane',
            'lastname' => 'Smith',
            'active' => 1,
            'birth_date' => '1995-05-05'
        ]];

        $controller = new PersonController();
        $controller->recordId = 'new'; // Simulate creation

        try {
            // ... (Reflection setup similar to above) ...
            $reflection = new \ReflectionClass($controller);
            $configMethod = $reflection->getMethod('buildConfiguration');
            $configMethod->setAccessible(true);
            $configMethod->invoke($controller);

            $method = $reflection->getMethod('saveRecord');
            $method->setAccessible(true);
            $method->invoke($controller);

            $this->fail("JSON success response was expected");
        } catch (HttpResponseException $e) {
            // 2. Verify Success Response
            $response = $e->getResponse();
            
            $this->assertArrayHasKey('status', $response);
            $this->assertEquals('success', $response['status']);
            $this->assertArrayHasKey('id', $response);

            // 3. Verify Persistence in DB
            $this->assertDatabaseHas('people', [
                'id' => $response['id'],
                'name' => 'Jane'
            ]);
        }
    }
}

Executing Tests ​

Tests must always be executed from within the Docker container to ensure the environment (PHP, extensions, database) is correct.

Main Commands ​

Execute all tests (Unit and Feature):

bash
docker exec alxarafe_php ./vendor/bin/phpunit

Execute only a specific suite:

bash
docker exec alxarafe_php ./vendor/bin/phpunit --testsuite Unit
docker exec alxarafe_php ./vendor/bin/phpunit --testsuite Feature

Execute a specific test file:

bash
docker exec alxarafe_php ./vendor/bin/phpunit skeleton/Tests/Unit/PersonTest.php

Style Verification (PSR-12) ​

It is critical to maintain code style in tests. Alxarafe enforces PSR-12. Test method names must be camelCase (e.g., testItDoSomething), not snake_case.

To verify style:

bash
docker exec alxarafe_php ./vendor/bin/phpcs --standard=PSR12 Tests/ skeleton/Tests/

If there are automatically correctable errors:

bash
docker exec alxarafe_php ./vendor/bin/phpcbf --standard=PSR12 Tests/ skeleton/Tests/