Testing & Code Quality
Master testing and code quality in GEMVC. Learn PHPStan Level 9 (the highest static analysis level), PHPUnit, and Pest testing frameworks. GEMVC is one of the few frameworks that supports PHPStan Level 9!
What You'll Learn
PHPStan Level 9
Static analysis and type safety
PHPUnit Testing
Unit and integration testing
Pest Testing
Modern testing framework alternative
Video Coming Soon...
Why GEMVC is Special for Testing
GEMVC is one of the few existing frameworks that supports PHPStan Level 9 - the highest level of static analysis in PHP! This means you can write type-safe, bug-free code with the best IDE support and catch errors before they reach production.
๐ฏ Why Other Frameworks Can't Achieve Level 9:
- โข Laravel/Symfony: Magic methods and dynamic facades break static analysis
- โข Eloquent/Doctrine: Complex ORMs make type inference impossible
- โข Dynamic Properties: Framework magic defeats static analysis
GEMVC's Advantage: Clean, explicit, type-safe design from the ground up!
- PHPStan Level 9 - Static analysis catches bugs before runtime
- PHPUnit - Industry-standard unit testing framework
- Pest - Modern, elegant testing framework alternative
- Automatic Configuration - All tools configured during
gemvc init - Pre-configured - Ready to use immediately after installation
Installation During gemvc init
Choose Your Testing Tools
During gemvc init, you'll be prompted to install testing and code quality tools:
# Initialize project
gemvc init
# When prompted:
# Install PHPStan? (y/n): y โ STRONGLY RECOMMENDED!
# Install testing framework? (y/n): y
# Choose testing framework:
# 1) PHPUnit
# 2) Pest
# Select: 1 or 2
โ ๏ธ Important:
PHPStan is strongly recommended! It will be automatically installed and configured in your project. Use it during development to catch type errors before they reach production.
What Gets Installed:
- โ PHPStan Level 9 (if selected) - Pre-configured
phpstan.neon - โ PHPUnit or Pest (if selected) - Test framework with configuration
- โ OpenSwoole stubs - For proper type checking with OpenSwoole
- โ Redis stubs - For connection type safety
- โ Test directory structure - Ready for your tests
PHPStan Level 9 - Static Analysis
What is PHPStan Level 9?
PHPStan Level 9 is the highest level of static analysis in PHP. It performs the most thorough type checking possible, catching bugs before runtime.
PHPStan Level 9 Catches:
- โ Type errors before runtime
- โ Null pointer exceptions
- โ Undefined method calls
- โ Incorrect array access
- โ Type mismatches
- โ Missing return types
- โ Incorrect property access
- โ And much more!
Running PHPStan
After installation, run PHPStan to check your code:
# Run PHPStan analysis
vendor/bin/phpstan analyse
# Or use composer script (if configured)
composer phpstan
# With baseline (ignore existing errors)
vendor/bin/phpstan analyse --generate-baseline
โ Without PHPStan (Bugs in Production):
<?php
public function getUser($id)
{
// โ No type hints
// โ Might return null - will cause error!
return $this->selectById($id)->name;
}
// Runtime error: Trying to get property 'name' of null
โ With PHPStan Level 9 (Caught at Development):
<?php
public function getUser(int $id): ?UserModel
{
// โ
Type hints
$user = $this->selectById($id);
if (!$user) {
return null; // โ
Handle null case
}
return $user; // โ
Type-safe!
}
// PHPStan ensures this is always safe
Benefits of PHPStan Level 9:
- โ Type Safety - Catch errors before they happen
- โ Better IDE Support - Full autocomplete, refactoring, navigation
- โ Cleaner Code - Forces explicit, readable types
- โ Fewer Bugs - Static analysis catches issues early
- โ Team Consistency - Everyone writes code the same way
- โ Self-Documenting - Types communicate intent clearly
PHPStan Configuration
Automatic Configuration
When you install PHPStan during gemvc init, it automatically creates a phpstan.neon configuration file:
parameters:
level: 9 # Highest level!
paths:
- app
excludePaths:
- app/vendor
checkMissingIterableValueType: true
checkGenericClassInNonGenericObjectType: true
checkUninitializedProperties: true
checkNullables: true
checkThisOnly: false
checkUnionTypes: true
checkExplicitMixedMissingReturn: true
What's Included:
- โ Level 9 - Highest static analysis level
- โ OpenSwoole Stubs - Proper type checking for OpenSwoole
- โ Redis Stubs - Connection type safety
- โ App Directory - Analyzes your application code
- โ All Checks Enabled - Maximum type safety
PHPUnit Testing
Industry-Standard Testing
PHPUnit is the most popular testing framework for PHP. GEMVC automatically configures it when you select it during gemvc init.
Running PHPUnit Tests
# Run all tests
vendor/bin/phpunit
# Run specific test file
vendor/bin/phpunit tests/UserTest.php
# Run with coverage
vendor/bin/phpunit --coverage-html coverage/
# Or use composer script
composer test
<?php
namespace Tests;
use PHPUnit\Framework\TestCase;
use App\Model\UserModel;
use App\Table\UserTable;
class UserModelTest extends TestCase
{
public function testCreateUser(): void
{
$user = new UserModel();
$user->name = 'John Doe';
$user->email = 'john@example.com';
$user->setPassword('password123');
$result = $user->createModel();
$this->assertEquals(201, $result->response_code);
$this->assertEquals('created', $result->message);
$this->assertNotNull($user->id);
}
public function testSelectByEmail(): void
{
$userTable = new UserTable();
$user = $userTable->selectByEmail('john@example.com');
$this->assertInstanceOf(UserModel::class, $user);
$this->assertEquals('john@example.com', $user->email);
}
public function testDuplicateEmail(): void
{
$user1 = new UserModel();
$user1->name = 'User 1';
$user1->email = 'duplicate@example.com';
$user1->setPassword('pass123');
$user1->createModel();
$user2 = new UserModel();
$user2->name = 'User 2';
$user2->email = 'duplicate@example.com';
$user2->setPassword('pass123');
$result = $user2->createModel();
$this->assertEquals(422, $result->response_code);
$this->assertStringContainsString('already exists', $result->service_message);
}
}
PHPUnit Features:
- โ Unit Tests - Test individual methods and classes
- โ Integration Tests - Test complete workflows
- โ Assertions - Comprehensive assertion library
- โ Test Coverage - Measure code coverage
- โ Mocking - Mock dependencies for isolated testing
Pest Testing
Modern, Elegant Testing
Pest is a modern testing framework built on top of PHPUnit. It provides a more elegant, readable syntax while maintaining all PHPUnit features.
Running Pest Tests
# Run all tests
vendor/bin/pest
# Run specific test file
vendor/bin/pest tests/UserTest.php
# Run with coverage
vendor/bin/pest --coverage
# Or use composer script
composer test
<?php
use App\Model\UserModel;
use App\Table\UserTable;
test('creates user successfully', function () {
$user = new UserModel();
$user->name = 'John Doe';
$user->email = 'john@example.com';
$user->setPassword('password123');
$result = $user->createModel();
expect($result->response_code)->toBe(201)
->and($result->message)->toBe('created')
->and($user->id)->not->toBeNull();
});
test('selects user by email', function () {
$userTable = new UserTable();
$user = $userTable->selectByEmail('john@example.com');
expect($user)->toBeInstanceOf(UserModel::class)
->and($user->email)->toBe('john@example.com');
});
test('prevents duplicate email', function () {
$user1 = new UserModel();
$user1->name = 'User 1';
$user1->email = 'duplicate@example.com';
$user1->setPassword('pass123');
$user1->createModel();
$user2 = new UserModel();
$user2->name = 'User 2';
$user2->email = 'duplicate@example.com';
$user2->setPassword('pass123');
$result = $user2->createModel();
expect($result->response_code)->toBe(422)
->and($result->service_message)->toContain('already exists');
});
Pest Advantages:
- โ Elegant Syntax - More readable test code
- โ Built on PHPUnit - All PHPUnit features available
- โ Better DX - Improved developer experience
- โ Fluent Assertions - Chainable expectations
- โ Modern PHP - Uses latest PHP features
Testing Best Practices
How to Use Testing Tools
Follow these best practices for effective testing and code quality:
๐ก Use PHPStan During Development
Run PHPStan regularly while developing, not just before commits:
# Run PHPStan frequently
vendor/bin/phpstan analyse
# Fix errors immediately
# Don't accumulate type errors
Tip: Set up a file watcher or run PHPStan in your IDE to catch errors as you type!
๐ก Write Tests for Critical Paths
Focus on testing business logic, API endpoints, and critical workflows. Don't test framework code - test your application code.
๐ก Test the 4-Layer Architecture
Test each layer appropriately:
- โข API Layer - Test schema validation, authentication
- โข Controller Layer - Test business logic orchestration
- โข Model Layer - Test data validations, transformations
- โข Table Layer - Test database queries (use test database!)
๐ก Use Test Database
Always use a separate test database. Never run tests against production data! Configure test database in phpunit.xml or Pest.php.
๐ก Run Tests Before Commits
Make it a habit to run both PHPStan and your test suite before committing code. This prevents broken code from reaching the repository.
Complete Testing Workflow
Here's a complete example showing how to use PHPStan and PHPUnit/Pest together:
Step 1: Write Code with Type Hints
<?php
namespace App\Model;
use App\Table\ProductTable;
use Gemvc\Http\JsonResponse;
use Gemvc\Http\Response;
class ProductModel extends ProductTable
{
public int $id;
public string $name;
public float $price;
public function createModel(): JsonResponse
{
// Business validation
if (empty($this->name)) {
return Response::unprocessableEntity('Name is required');
}
if ($this->price <= 0) {
return Response::unprocessableEntity('Price must be greater than 0');
}
// Database operation
$this->insertSingleQuery();
if ($this->getError()) {
return Response::internalError($this->getError());
}
return Response::created($this, 1, 'Product created successfully');
}
}
Step 2: Run PHPStan
# Check code quality
vendor/bin/phpstan analyse
# PHPStan will verify:
# โ All type hints are correct
# โ No null pointer exceptions
# โ All return types match
# โ No undefined method calls
Step 3: Write Tests
<?php
namespace Tests;
use PHPUnit\Framework\TestCase;
use App\Model\ProductModel;
class ProductModelTest extends TestCase
{
public function testCreateProductSuccess(): void
{
$product = new ProductModel();
$product->name = 'Laptop';
$product->price = 999.99;
$result = $product->createModel();
$this->assertEquals(201, $result->response_code);
$this->assertEquals('created', $result->message);
$this->assertNotNull($product->id);
}
public function testCreateProductWithoutName(): void
{
$product = new ProductModel();
$product->price = 999.99;
// name is empty
$result = $product->createModel();
$this->assertEquals(422, $result->response_code);
$this->assertStringContainsString('Name is required', $result->service_message);
}
public function testCreateProductWithInvalidPrice(): void
{
$product = new ProductModel();
$product->name = 'Laptop';
$product->price = -100; // Invalid price
$result = $product->createModel();
$this->assertEquals(422, $result->response_code);
$this->assertStringContainsString('Price must be greater than 0', $result->service_message);
}
}
Step 4: Run Tests
# Run all tests
vendor/bin/phpunit
# Or with Pest
vendor/bin/pest
# Verify all tests pass
# โ testCreateProductSuccess
# โ testCreateProductWithoutName
# โ testCreateProductWithInvalidPrice
Important Notes
- PHPStan is Strongly Recommended: GEMVC is one of the few frameworks that supports PHPStan Level 9. Use it during development to catch type errors before they reach production!
-
Automatic Configuration: When you choose PHPStan or testing frameworks during
gemvc init, they are automatically installed and configured. No manual setup needed! - Use During Development: Don't wait until the end to run PHPStan. Use it continuously during development to catch errors as you write code.
- Test Database: Always use a separate test database. Never run tests against production data! Configure it in your test framework configuration file.
- Level 9 Advantage: GEMVC's clean architecture allows PHPStan Level 9, which other frameworks can't achieve due to magic methods and dynamic properties.
๐งช Testing & Quality Complete!
Excellent! You've learned how to use PHPStan Level 9, PHPUnit, and Pest in GEMVC. Remember: GEMVC is one of the few frameworks that supports PHPStan Level 9 - use it during development to write type-safe, bug-free code!