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!