Files
slim-blog/tests/Category/CategoryServiceTest.php
2026-03-16 02:33:18 +01:00

190 lines
6.3 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Category;
use App\Category\Category;
use App\Category\CategoryRepositoryInterface;
use App\Category\CategoryService;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
/**
* Tests unitaires pour CategoryService.
*
* Vérifie la création (génération de slug, unicité du nom, validation du modèle)
* et la suppression (blocage si articles rattachés).
* Le repository est remplacé par un mock pour isoler le service.
*/
#[\PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations]
final class CategoryServiceTest extends TestCase
{
/** @var CategoryRepositoryInterface&MockObject */
private CategoryRepositoryInterface $repository;
private CategoryService $service;
protected function setUp(): void
{
$this->repository = $this->createMock(CategoryRepositoryInterface::class);
$this->service = new CategoryService($this->repository);
}
// ── create ─────────────────────────────────────────────────────
/**
* create() doit générer le slug depuis le nom et persister la catégorie.
*/
public function testCreateGeneratesSlugAndPersists(): void
{
$this->repository->method('nameExists')->willReturn(false);
$this->repository->expects($this->once())
->method('create')
->with($this->callback(fn (Category $c) =>
$c->getName() === 'Développement web'
&& $c->getSlug() === 'developpement-web'
))
->willReturn(1);
$id = $this->service->create('Développement web');
$this->assertSame(1, $id);
}
/**
* create() doit trimmer le nom avant de générer le slug.
*/
public function testCreateTrimsName(): void
{
$this->repository->method('nameExists')->willReturn(false);
$this->repository->expects($this->once())
->method('create')
->with($this->callback(fn (Category $c) => $c->getName() === 'PHP'))
->willReturn(2);
$this->service->create(' PHP ');
}
/**
* create() doit lever InvalidArgumentException si le slug généré est vide.
*/
public function testCreateNonAsciiNameThrowsException(): void
{
$this->repository->expects($this->never())->method('create');
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('slug URL valide');
$this->service->create('日本語');
}
/**
* create() doit lever InvalidArgumentException si le nom est déjà utilisé.
*/
public function testCreateDuplicateNameThrowsException(): void
{
$this->repository->method('nameExists')->willReturn(true);
$this->repository->expects($this->never())->method('create');
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('déjà utilisé');
$this->service->create('PHP');
}
/**
* create() doit lever InvalidArgumentException si le nom est vide.
*/
public function testCreateEmptyNameThrowsException(): void
{
$this->repository->method('nameExists')->willReturn(false);
$this->repository->expects($this->never())->method('create');
$this->expectException(\InvalidArgumentException::class);
$this->service->create('');
}
/**
* create() doit lever InvalidArgumentException si le nom dépasse 100 caractères.
*/
public function testCreateNameTooLongThrowsException(): void
{
$longName = str_repeat('a', 101);
$this->repository->method('nameExists')->willReturn(false);
$this->repository->expects($this->never())->method('create');
$this->expectException(\InvalidArgumentException::class);
$this->service->create($longName);
}
// ── delete ─────────────────────────────────────────────────────
/**
* delete() doit supprimer la catégorie si elle ne contient aucun article.
*/
public function testDeleteSucceedsWhenNoPosts(): void
{
$category = new Category(5, 'PHP', 'php');
$this->repository->expects($this->once())->method('hasPost')->with(5)->willReturn(false);
$this->repository->expects($this->once())->method('delete')->with(5);
$this->service->delete($category);
}
/**
* delete() doit lever InvalidArgumentException si des articles sont rattachés.
*/
public function testDeleteBlockedWhenPostsAttached(): void
{
$category = new Category(5, 'PHP', 'php');
$this->repository->expects($this->once())->method('hasPost')->with(5)->willReturn(true);
$this->repository->expects($this->never())->method('delete');
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('contient des articles');
$this->service->delete($category);
}
// ── Lectures déléguées ─────────────────────────────────────────
/**
* findAll() doit déléguer au repository et retourner son résultat.
*/
public function testFindAllDelegatesToRepository(): void
{
$cats = [new Category(1, 'PHP', 'php'), new Category(2, 'CSS', 'css')];
$this->repository->method('findAll')->willReturn($cats);
$this->assertSame($cats, $this->service->findAll());
}
/**
* findById() doit retourner null si la catégorie n'existe pas.
*/
public function testFindByIdReturnsNullWhenMissing(): void
{
$this->repository->method('findById')->willReturn(null);
$this->assertNull($this->service->findById(99));
}
/**
* findBySlug() doit retourner la catégorie correspondante.
*/
public function testFindBySlugReturnsCategoryWhenFound(): void
{
$cat = new Category(3, 'PHP', 'php');
$this->repository->expects($this->once())->method('findBySlug')->with('php')->willReturn($cat);
$this->assertSame($cat, $this->service->findBySlug('php'));
}
}