Apprendre à écrire des tests automatisés
pour vos applications Symfony
Objectifs :
Testent une fonction ou méthode isolée
Testent l'interaction entre plusieurs composants
Testent une fonctionnalité complÚte du point de vue utilisateur
Depuis Symfony 4.1, PHPUnit est installé automatiquement :
mon-projet/ âââ src/ âââ tests/ â Dossier des tests â âââ Unit/ â Tests unitaires â âââ Integration/ â Tests d'intĂ©gration â âââ Functional/ â Tests fonctionnels âââ phpunit.xml.dist â Configuration PHPUnit âââ .env.test â Variables d'environnement pour les tests
phpunit.xml.dist configure PHPUnit pour votre projet Symfony.
<?php
// src/Service/Calculator.php
namespace App\Service;
class Calculator
{
public function add(int $a, int $b): int
{
return $a + $b;
}
public function divide(int $a, int $b): float
{
if ($b === 0) {
throw new \InvalidArgumentException('Division by zero');
}
return $a / $b;
}
}<?php
// tests/Unit/Service/CalculatorTest.php
namespace App\Tests\Unit\Service;
use App\Service\Calculator;
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase
{
public function testAdd(): void
{
$calculator = new Calculator();
$result = $calculator->add(2, 3);
$this->assertEquals(5, $result);
}
}PHPUnit 9.5.10 by Sebastian Bergmann and contributors. . 1 / 1 (100%) Time: 00:00.001, Memory: 6.00 MB OK (1 test, 1 assertion)
. indique un test réussi. Un F indiquerait un échec.
// ĂgalitĂ© stricte $this->assertEquals(5, $result); // IdentitĂ© (===) $this->assertSame(5, $result); // Non Ă©galitĂ© $this->assertNotEquals(3, $result);
$this->assertTrue($condition); $this->assertFalse($condition);
$this->assertNull($value); $this->assertNotNull($value);
$this->assertContains('item', $array);
$this->assertStringContains('text', $string);
$this->assertCount(3, $array);$this->expectException(
\InvalidArgumentException::class
);
$calculator->divide(5, 0);assertEquals pour les valeurs et assertSame pour l'identité stricte.
<?php
// tests/Unit/Service/CalculatorTest.php
namespace App\Tests\Unit\Service;
use App\Service\Calculator;
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase
{
private Calculator $calculator;
protected function setUp(): void
{
$this->calculator = new Calculator();
}
public function testAdd(): void
{
$result = $this->calculator->add(2, 3);
$this->assertEquals(5, $result);
}
public function testDivide(): void
{
$result = $this->calculator->divide(10, 2);
$this->assertEquals(5.0, $result);
}
public function testDivideByZeroThrowsException(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Division by zero');
$this->calculator->divide(10, 0);
}
}setUp() s'exécute avant chaque test. Utilisez-la pour initialiser vos objets.
<?php
// src/Service/UserService.php
namespace App\Service;
use App\Repository\UserRepository;
class UserService
{
public function __construct(
private UserRepository $userRepository
) {}
public function getUserCount(): int
{
return count($this->userRepository->findAll());
}
public function isEmailTaken(string $email): bool
{
return $this->userRepository->findOneBy(['email' => $email]) !== null;
}
}<?php
// tests/Integration/Service/UserServiceTest.php
namespace App\Tests\Integration\Service;
use App\Service\UserService;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class UserServiceTest extends KernelTestCase
{
public function testGetUserCount(): void
{
self::bootKernel();
$container = static::getContainer();
$userService = $container->get(UserService::class);
$count = $userService->getUserCount();
$this->assertIsInt($count);
$this->assertGreaterThanOrEqual(0, $count);
}
}<?php
// tests/Unit/Service/UserServiceTest.php
namespace App\Tests\Unit\Service;
use App\Entity\User;
use App\Repository\UserRepository;
use App\Service\UserService;
use PHPUnit\Framework\TestCase;
class UserServiceTest extends TestCase
{
public function testIsEmailTaken(): void
{
// Créer un mock du repository
$mockRepository = $this->createMock(UserRepository::class);
// Définir le comportement attendu
$mockRepository->method('findOneBy')
->with(['email' => 'test@example.com'])
->willReturn(new User());
$userService = new UserService($mockRepository);
$result = $userService->isEmailTaken('test@example.com');
$this->assertTrue($result);
}
}<?php
// tests/Functional/Controller/HomeControllerTest.php
namespace App\Tests\Functional\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class HomeControllerTest extends WebTestCase
{
public function testHomePage(): void
{
$client = static::createClient();
$crawler = $client->request('GET', '/');
$this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('h1', 'Bienvenue');
}
public function testContactForm(): void
{
$client = static::createClient();
$crawler = $client->request('GET', '/contact');
$form = $crawler->selectButton('Envoyer')->form([
'contact[name]' => 'John Doe',
'contact[email]' => 'john@example.com',
'contact[message]' => 'Hello world'
]);
$client->submit($form);
$this->assertResponseRedirects('/contact/success');
}
}WebTestCase simule un navigateur et permet de tester vos contrĂŽleurs de bout en bout.
# .env.test DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_test.db" # ou DATABASE_URL="mysql://user:pass@127.0.0.1:3306/myapp_test"
<?php
// tests/Functional/Repository/UserRepositoryTest.php
namespace App\Tests\Functional\Repository;
use App\Entity\User;
use App\Repository\UserRepository;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Doctrine\ORM\EntityManagerInterface;
class UserRepositoryTest extends KernelTestCase
{
private EntityManagerInterface $entityManager;
private UserRepository $userRepository;
protected function setUp(): void
{
$kernel = self::bootKernel();
$this->entityManager = $kernel->getContainer()
->get('doctrine')->getManager();
$this->userRepository = $this->entityManager
->getRepository(User::class);
}
public function testFindByEmail(): void
{
$user = new User();
$user->setEmail('test@example.com');
$user->setName('Test User');
$this->entityManager->persist($user);
$this->entityManager->flush();
$foundUser = $this->userRepository
->findOneBy(['email' => 'test@example.com']);
$this->assertNotNull($foundUser);
$this->assertEquals('Test User', $foundUser->getName());
}
}<?php
// src/DataFixtures/TestFixtures.php
namespace App\DataFixtures;
use App\Entity\User;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
class TestFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
for ($i = 1; $i <= 10; $i++) {
$user = new User();
$user->setEmail("user{$i}@example.com");
$user->setName("User {$i}");
$manager->persist($user);
}
$manager->flush();
}
}// Dans votre test
protected function setUp(): void
{
parent::setUp();
// Charger les fixtures
$this->loadFixtures([TestFixtures::class]);
}testUserCanLoginWithValidCredentialspublic function testCalculateTotal(): void
{
// Arrange
$cart = new Cart();
$cart->addItem(new Item(10.0));
// Act
$total = $cart->getTotal();
// Assert
$this->assertEquals(10.0, $total);
}# .github/workflows/tests.yml
name: Tests
on: [push, pull_request]
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
- run: composer install
- run: php bin/phpunit