Getting Started with Testing

Guide for testing applications built with Stilmark Base.

Testing Setup

PHPUnit Installation

Add PHPUnit to your project:

composer require --dev phpunit/phpunit

Directory Structure

Organize your tests following PSR-4 conventions:

tests/
├── Unit/
│   ├── EnvTest.php
│   ├── RequestTest.php
│   └── ControllerTest.php
├── Integration/
│   ├── AuthTest.php
│   └── RouterTest.php
└── bootstrap.php

Test Bootstrap

Create tests/bootstrap.php:

<?php
require __DIR__ . '/../vendor/autoload.php';

use Stilmark\Base\Env;

// Load test environment
Env::load(__DIR__ . '/../.env.testing');

// Set test-specific configurations
Env::set('APP_ENV', 'testing');
Env::set('DB_DATABASE', 'baseapp_test');

Unit Testing Examples

Testing Environment Management

<?php
namespace Tests\Unit;

use PHPUnit\Framework\TestCase;
use Stilmark\Base\Env;

class EnvTest extends TestCase
{
    protected function setUp(): void
    {
        // Reset environment for each test
        Env::clear();
    }

    public function testCanSetAndGetEnvironmentVariable()
    {
        Env::set('TEST_VAR', 'test_value');
        
        $this->assertEquals('test_value', Env::get('TEST_VAR'));
    }

    public function testReturnsDefaultWhenVariableNotSet()
    {
        $result = Env::get('NON_EXISTENT', 'default');
        
        $this->assertEquals('default', $result);
    }

    public function testCanLoadFromFile()
    {
        $envContent = "TEST_FROM_FILE=file_value\n";
        $tempFile = tempnam(sys_get_temp_dir(), 'env_test');
        file_put_contents($tempFile, $envContent);

        Env::load($tempFile);
        
        $this->assertEquals('file_value', Env::get('TEST_FROM_FILE'));
        
        unlink($tempFile);
    }
}

Testing Request Handling

<?php
namespace Tests\Unit;

use PHPUnit\Framework\TestCase;
use Stilmark\Base\Request;

class RequestTest extends TestCase
{
    protected function setUp(): void
    {
        // Reset superglobals
        $_GET = [];
        $_POST = [];
        $_SERVER = [];
    }

    public function testCanGetQueryParameters()
    {
        $_GET['user_id'] = '123';
        
        $request = new Request();
        
        $this->assertEquals('123', $request->get('user_id'));
    }

    public function testCanGetPostData()
    {
        $_POST['username'] = 'testuser';
        
        $request = new Request();
        
        $this->assertEquals('testuser', $request->post('username'));
    }

    public function testCanParseJsonInput()
    {
        $jsonData = json_encode(['key' => 'value']);
        
        // Mock php://input
        $request = $this->getMockBuilder(Request::class)
            ->onlyMethods(['getJsonInput'])
            ->getMock();
        
        $request->method('getJsonInput')->willReturn($jsonData);
        
        $this->assertEquals(['key' => 'value'], $request->json());
    }
}

Testing Controllers

<?php
namespace Tests\Unit;

use PHPUnit\Framework\TestCase;
use Stilmark\Base\Controller;

class TestController extends Controller
{
    public function testAction()
    {
        return $this->json(['status' => 'success']);
    }
}

class ControllerTest extends TestCase
{
    public function testJsonResponse()
    {
        $controller = new TestController();
        
        ob_start();
        $controller->testAction();
        $output = ob_get_clean();
        
        $this->assertJson($output);
        $this->assertStringContainsString('success', $output);
    }
}

Integration Testing

Testing Authentication Flow

<?php
namespace Tests\Integration;

use PHPUnit\Framework\TestCase;
use Stilmark\Base\Auth;
use Stilmark\Base\Env;

class AuthTest extends TestCase
{
    protected function setUp(): void
    {
        // Set up test OAuth credentials
        Env::set('GOOGLE_CLIENT_ID', 'test_client_id');
        Env::set('GOOGLE_CLIENT_SECRET', 'test_secret');
        Env::set('GOOGLE_REDIRECT_URI', 'http://localhost/callback');
    }

    public function testAuthInitialization()
    {
        $auth = new Auth();
        
        $this->assertInstanceOf(Auth::class, $auth);
    }

    public function testCalloutGeneratesRedirectUrl()
    {
        $auth = new Auth();
        
        ob_start();
        $auth->callout();
        $output = ob_get_clean();
        
        // Should redirect to Google OAuth
        $this->assertStringContainsString('accounts.google.com', $output);
    }
}

Testing Router Integration

<?php
namespace Tests\Integration;

use PHPUnit\Framework\TestCase;
use Stilmark\Base\Router;

class RouterTest extends TestCase
{
    public function testRouteDispatch()
    {
        $router = new Router();
        
        $router->addRoute('GET', '/test', function() {
            echo 'Test route works';
        });
        
        // Mock request
        $_SERVER['REQUEST_METHOD'] = 'GET';
        $_SERVER['REQUEST_URI'] = '/test';
        
        ob_start();
        $router->dispatch();
        $output = ob_get_clean();
        
        $this->assertEquals('Test route works', $output);
    }
}

Testing Best Practices

Environment Isolation

Create a separate .env.testing file:

# .env.testing
APP_ENV=testing
DB_DATABASE=baseapp_test
CACHE_DRIVER=array
SESSION_DRIVER=array

Mocking External Services

public function testOAuthWithMockedService()
{
    $mockProvider = $this->createMock(GoogleProvider::class);
    $mockProvider->method('getAuthorizationUrl')
               ->willReturn('http://mock-oauth-url');
    
    $auth = new Auth($mockProvider);
    // Test with mocked provider
}

Database Testing

protected function setUp(): void
{
    // Start transaction
    DB::beginTransaction();
}

protected function tearDown(): void
{
    // Rollback transaction
    DB::rollback();
}

Running Tests

Basic Test Execution

# Run all tests
./vendor/bin/phpunit

# Run specific test suite
./vendor/bin/phpunit tests/Unit

# Run with coverage
./vendor/bin/phpunit --coverage-html coverage/

PHPUnit Configuration

Create phpunit.xml:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php"
         colors="true"
         verbose="true">
    <testsuites>
        <testsuite name="Unit">
            <directory>tests/Unit</directory>
        </testsuite>
        <testsuite name="Integration">
            <directory>tests/Integration</directory>
        </testsuite>
    </testsuites>
    <coverage>
        <include>
            <directory>src/</directory>
        </include>
    </coverage>
</phpunit>

Continuous Integration

GitHub Actions Example

Create .github/workflows/tests.yml:

name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    strategy:
      matrix:
        php: [8.2, 8.3]
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: ${{ matrix.php }}
        extensions: json, curl
    
    - name: Install dependencies
      run: composer install --prefer-dist --no-progress
    
    - name: Run tests
      run: ./vendor/bin/phpunit

Performance Testing

Benchmarking Routes

public function testRoutePerformance()
{
    $router = new Router();
    $router->addRoute('GET', '/api/users/{id}', 'UserController@show');
    
    $start = microtime(true);
    
    for ($i = 0; $i < 1000; $i++) {
        $_SERVER['REQUEST_URI'] = '/api/users/123';
        $router->dispatch();
    }
    
    $duration = microtime(true) - $start;
    
    $this->assertLessThan(1.0, $duration, 'Route resolution too slow');
}

This testing guide provides comprehensive examples for testing Stilmark Base applications with proper isolation, mocking, and CI integration.

Last updated