This guide covers testing practices for ReSymf-CMS using PHPUnit.
# All tests
docker-compose exec php bin/phpunit
# Specific test file
docker-compose exec php bin/phpunit tests/Unit/Entity/UserTest.php
# Specific test method
docker-compose exec php bin/phpunit --filter testConstructorSetsDefaults
# With coverage
docker-compose exec php bash -c "XDEBUG_MODE=coverage bin/phpunit --coverage-html var/coverage"# All tests
bin/phpunit
# With coverage
XDEBUG_MODE=coverage bin/phpunit --coverage-html var/coveragetests/
├── Unit/
│ ├── Entity/ # Entity unit tests
│ ├── Service/ # Service unit tests
│ └── Form/ # Form type tests
├── Functional/
│ └── Controller/ # Controller tests
└── bootstrap.php # Test bootstrap
Test entity behavior in tests/Unit/Entity/:
<?php
declare(strict_types=1);
namespace App\Tests\Unit\Entity;
use App\Entity\Category;
use PHPUnit\Framework\TestCase;
class CategoryTest extends TestCase
{
public function testConstructorSetsDefaults(): void
{
$category = new Category();
$this->assertNull($category->getId());
$this->assertTrue($category->getIsActive());
$this->assertInstanceOf(\DateTimeImmutable::class, $category->getCreatedAt());
$this->assertNull($category->getUpdatedAt());
}
public function testSetName(): void
{
$category = new Category();
$result = $category->setName('Test Category');
$this->assertSame('Test Category', $category->getName());
$this->assertSame($category, $result); // Fluent interface
}
public function testSetSlug(): void
{
$category = new Category();
$category->setSlug('test-category');
$this->assertSame('test-category', $category->getSlug());
}
public function testToggleActive(): void
{
$category = new Category();
$this->assertTrue($category->getIsActive());
$category->setIsActive(false);
$this->assertFalse($category->getIsActive());
$category->setIsActive(true);
$this->assertTrue($category->getIsActive());
}
public function testToString(): void
{
$category = new Category();
$category->setName('My Category');
$this->assertSame('My Category', (string) $category);
}
}Test controllers in tests/Functional/Controller/:
<?php
declare(strict_types=1);
namespace App\Tests\Functional\Controller;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class AdminDashboardControllerTest extends WebTestCase
{
public function testDashboardRequiresLogin(): void
{
$client = static::createClient();
$client->request('GET', '/admin/dashboard');
$this->assertResponseRedirects('/login');
}
public function testDashboardAccessibleWhenLoggedIn(): void
{
$client = static::createClient();
// Get admin user
$userRepository = static::getContainer()->get(UserRepository::class);
$adminUser = $userRepository->findOneBy(['username' => 'admin']);
// Login
$client->loginUser($adminUser);
$client->request('GET', '/admin/dashboard');
$this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('h1', 'Dashboard');
}
}Test repositories with database:
<?php
declare(strict_types=1);
namespace App\Tests\Functional\Repository;
use App\Entity\Category;
use App\Repository\CategoryRepository;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class CategoryRepositoryTest extends KernelTestCase
{
private CategoryRepository $repository;
protected function setUp(): void
{
self::bootKernel();
$this->repository = static::getContainer()->get(CategoryRepository::class);
}
public function testFindActive(): void
{
$categories = $this->repository->findActive();
foreach ($categories as $category) {
$this->assertTrue($category->getIsActive());
}
}
}Use PHPUnit mocks for isolated unit tests:
public function testServiceWithMockedDependency(): void
{
$repository = $this->createMock(CategoryRepository::class);
$repository->expects($this->once())
->method('findAll')
->willReturn([new Category()]);
$service = new CategoryService($repository);
$result = $service->getAllCategories();
$this->assertCount(1, $result);
}Configure a test database in .env.test:
DATABASE_URL="mysql://root:password@127.0.0.1:3306/resymf_test?serverVersion=8.0"Setup test database:
# Create test database
php bin/console doctrine:database:create --env=test
# Run migrations
php bin/console doctrine:migrations:migrate --env=test --no-interaction
# Load fixtures
php bin/console doctrine:fixtures:load --env=test --no-interactiondocker-compose exec php vendor/bin/phpstan analyse# Check style
docker-compose exec php vendor/bin/php-cs-fixer fix --dry-run --diff
# Fix style
docker-compose exec php vendor/bin/php-cs-fixer fixTests run automatically in GitHub Actions (.github/workflows/symfony-ci.yml):
- code-quality - PHPStan, PHP-CS-Fixer
- phpunit-tests - All PHPUnit tests
- security-audit - Composer audit
- doctrine-validation - Schema validation
- lint - PHP, Twig, YAML linting
When writing tests:
- Test entity constructors and defaults
- Test getters/setters with fluent interface
- Test relationships (add/remove)
- Test controller routes require auth
- Test form validation
- Run full test suite before commit