📖 Symfony Framework Toàn tập - Testing trong Symfony
60 phút

Testing trong Symfony

Setup

composer require --dev symfony/test-pack

Unit Tests

<?php

namespace App\Tests\Service;

use App\Entity\User;
use App\Repository\UserRepository;
use App\Service\UserService;
use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;

class UserServiceTest extends TestCase
{
    private UserService $service;
    private UserRepository $repository;
    private EntityManagerInterface $em;
    private UserPasswordHasherInterface $hasher;

    protected function setUp(): void
    {
        $this->repository = $this->createMock(UserRepository::class);
        $this->em = $this->createMock(EntityManagerInterface::class);
        $this->hasher = $this->createMock(UserPasswordHasherInterface::class);

        $this->service = new UserService(
            $this->repository,
            $this->em,
            $this->hasher
        );
    }

    public function testCreateSuccess(): void
    {
        $this->repository
            ->method('findByEmail')
            ->willReturn(null);

        $this->hasher
            ->method('hashPassword')
            ->willReturn('hashed');

        $this->em
            ->expects($this->once())
            ->method('persist');
        $this->em
            ->expects($this->once())
            ->method('flush');

        $user = $this->service->create('John', 'john@example.com', 'pass');

        $this->assertSame('John', $user->getName());
        $this->assertSame('john@example.com', $user->getEmail());
    }

    public function testCreateDuplicateThrows(): void
    {
        $existing = new User();
        $this->repository
            ->method('findByEmail')
            ->willReturn($existing);

        $this->expectException(\DomainException::class);

        $this->service->create('John', 'john@example.com', 'pass');
    }
}

Functional Tests

<?php

namespace App\Tests\Controller;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use Symfony\Component\BrowserKit\AbstractBrowser;

class UserControllerTest extends WebTestCase
{
    private ?AbstractBrowser $client = null;

    protected function setUp(): void
    {
        $this->client = static::createClient();
    }

    public function testIndexRequiresAuth(): void
    {
        $this->client->request('GET', '/users');

        $this->assertResponseRedirects('/login');
    }

    public function testIndexWithAuth(): void
    {
        $user = $this->createUser('john@example.com');
        $this->client->loginUser($user);

        $this->client->request('GET', '/users');

        $this->assertResponseIsSuccessful();
        $this->assertSelectorTextContains('h1', 'Users');
    }

    public function testCreateUser(): void
    {
        $this->client->request('GET', '/register');
        $this->assertResponseIsSuccessful();

        $this->client->submitForm('Register', [
            'registration_form[name]' => 'John Doe',
            'registration_form[email]' => 'john@example.com',
            'registration_form[plainPassword][first]' => 'Password123',
            'registration_form[plainPassword][second]' => 'Password123',
            'registration_form[agreeTerms]' => true,
        ]);

        $this->assertResponseRedirects('/login');
        $this->client->followRedirect();

        $em = static::getContainer()->get(EntityManagerInterface::class);
        $user = $em->getRepository(User::class)
            ->findOneBy(['email' => 'john@example.com']);
        $this->assertNotNull($user);
    }

    private function createUser(string $email): User
    {
        $em = static::getContainer()->get(EntityManagerInterface::class);
        $user = new User();
        $user->setName('Test');
        $user->setEmail($email);
        $user->setPassword('hashed');
        $em->persist($user);
        $em->flush();
        return $user;
    }
}

API Tests

public function testCreateProductAPI(): void
{
    $this->client->jsonRequest('POST', '/api/products', [
        'name' => 'Laptop',
        'price' => '1000.00',
        'stock' => 10,
    ]);

    $this->assertResponseStatusCodeSame(201);
    $this->assertJsonContains(['name' => 'Laptop']);

    $data = json_decode($this->client->getResponse()->getContent(), true);
    $this->assertArrayHasKey('id', $data);
}

Fixtures trong tests

use App\DataFixtures\UserFixtures;
use Liip\TestFixturesBundle\Test\FixturesTrait;

class UserFunctionalTest extends WebTestCase
{
    use FixturesTrait;

    public function testWithFixtures(): void
    {
        $this->loadFixtures([UserFixtures::class]);
        // ...
    }
}

Test cho Form Validation

public function testRegisterWithInvalidEmail(): void
{
    $this->client->request('GET', '/register');

    $this->client->submitForm('Register', [
        'registration_form[name]' => 'J',
        'registration_form[email]' => 'invalid-email',
        'registration_form[plainPassword][first]' => '123',
        'registration_form[plainPassword][second]' => '456',
    ]);

    $this->assertSelectorExists('.form-error-message');
    $this->assertSelectorTextContains('body', 'Passwords must match');
}

Test Database

.env.test

DATABASE_URL="sqlite:///%kernel.project_dir%/var/test.db"

Config

# config/packages/test/doctrine.yaml
doctrine:
    dbal:
        driver: pdo_sqlite

Chạy tests

# Chạy tất cả
php bin/phpunit

# Chạy 1 file
php bin/phpunit tests/Service/UserServiceTest.php

# Filter
php bin/phpunit --filter testCreateSuccess

# Coverage
php bin/phpunit --coverage-html var/coverage

Bài tập thực hành

Hãy viết tests cho API Product!

📝 Bài tập (1)

  1. Viết unit và functional tests