Generates PHPUnit unit tests for PHP 8.4. Creates isolated tests with AAA pattern, proper naming, attributes, and one behavior per test. Supports Value Objects, Entities, Services.
From accnpx claudepluginhub dykyi-roman/awesome-claude-code --plugin accThis skill uses the workspace's default tool permissions.
Generates PHPUnit 11+ unit tests for PHP 8.4 classes.
<?php
declare(strict_types=1);
namespace Tests\Unit\{Namespace};
use {FullyQualifiedClassName};
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
#[Group('unit')]
#[CoversClass({ClassName}::class)]
final class {ClassName}Test extends TestCase
{
private {ClassName} $sut;
protected function setUp(): void
{
$this->sut = new {ClassName}(/* dependencies */);
}
public function test_{method}_{scenario}_{expected}(): void
{
// Arrange
{arrange_code}
// Act
{act_code}
// Assert
{assert_code}
}
}
test_{method}_{scenario}_{expected}
| Part | Description | Example |
|---|---|---|
{method} | Method under test | calculate_total |
{scenario} | Input condition | with_discount |
{expected} | Expected outcome | returns_reduced_price |
Examples:
test_confirm_when_pending_changes_status_to_confirmed
test_create_with_invalid_email_throws_exception
test_equals_with_same_value_returns_true
test_add_item_increases_total
#[Group('unit')]
#[CoversClass(Email::class)]
final class EmailTest extends TestCase
{
// Creation - valid
public function test_creates_with_valid_email(): void
{
$email = new Email('user@example.com');
self::assertSame('user@example.com', $email->value);
}
// Validation - invalid
public function test_throws_for_empty_value(): void
{
$this->expectException(InvalidArgumentException::class);
new Email('');
}
public function test_throws_for_invalid_format(): void
{
$this->expectException(InvalidArgumentException::class);
new Email('not-an-email');
}
// Equality
public function test_equals_returns_true_for_same_value(): void
{
$email1 = new Email('user@example.com');
$email2 = new Email('user@example.com');
self::assertTrue($email1->equals($email2));
}
public function test_equals_returns_false_for_different_value(): void
{
$email1 = new Email('user@example.com');
$email2 = new Email('other@example.com');
self::assertFalse($email1->equals($email2));
}
}
#[Group('unit')]
#[CoversClass(Order::class)]
final class OrderTest extends TestCase
{
private Order $order;
protected function setUp(): void
{
$this->order = new Order(
OrderId::fromString('order-123'),
CustomerId::fromString('customer-456')
);
}
// Identity
public function test_has_unique_identity(): void
{
self::assertSame('order-123', $this->order->id()->toString());
}
// Initial state
public function test_is_pending_when_created(): void
{
self::assertTrue($this->order->isPending());
}
// State transitions - valid
public function test_confirm_changes_status_to_confirmed(): void
{
$this->order->addItem(ProductMother::book(), 1);
$this->order->confirm();
self::assertTrue($this->order->isConfirmed());
}
// State transitions - invalid
public function test_confirm_throws_when_already_confirmed(): void
{
$this->order->addItem(ProductMother::book(), 1);
$this->order->confirm();
$this->expectException(DomainException::class);
$this->order->confirm();
}
// Business rules
public function test_add_item_increases_total(): void
{
$this->order->addItem(ProductMother::withPrice(Money::EUR(100)), 2);
self::assertEquals(Money::EUR(200), $this->order->total());
}
// Domain events
public function test_records_order_confirmed_event(): void
{
$this->order->addItem(ProductMother::book(), 1);
$this->order->confirm();
$events = $this->order->releaseEvents();
self::assertCount(1, $events);
self::assertInstanceOf(OrderConfirmedEvent::class, $events[0]);
}
}
#[Group('unit')]
#[CoversClass(TransferMoneyService::class)]
final class TransferMoneyServiceTest extends TestCase
{
private TransferMoneyService $service;
private InMemoryAccountRepository $repository;
private CollectingEventDispatcher $dispatcher;
protected function setUp(): void
{
$this->repository = new InMemoryAccountRepository();
$this->dispatcher = new CollectingEventDispatcher();
$this->service = new TransferMoneyService(
$this->repository,
$this->dispatcher
);
}
public function test_transfers_money_between_accounts(): void
{
// Arrange
$source = AccountMother::withBalance(Money::EUR(1000));
$target = AccountMother::withBalance(Money::EUR(500));
$this->repository->save($source);
$this->repository->save($target);
// Act
$this->service->transfer(
$source->id(),
$target->id(),
Money::EUR(300)
);
// Assert
$updatedSource = $this->repository->findById($source->id());
$updatedTarget = $this->repository->findById($target->id());
self::assertEquals(Money::EUR(700), $updatedSource->balance());
self::assertEquals(Money::EUR(800), $updatedTarget->balance());
}
public function test_throws_for_insufficient_funds(): void
{
// Arrange
$source = AccountMother::withBalance(Money::EUR(100));
$target = AccountMother::withBalance(Money::EUR(500));
$this->repository->save($source);
$this->repository->save($target);
// Assert
$this->expectException(InsufficientFundsException::class);
// Act
$this->service->transfer(
$source->id(),
$target->id(),
Money::EUR(300)
);
}
}
use PHPUnit\Framework\Attributes\DataProvider;
#[DataProvider('validEmailsProvider')]
public function test_accepts_valid_formats(string $email): void
{
$vo = new Email($email);
self::assertSame($email, $vo->value);
}
public static function validEmailsProvider(): array
{
return [
'simple' => ['user@example.com'],
'with subdomain' => ['user@mail.example.com'],
'with plus' => ['user+tag@example.com'],
'with dots' => ['first.last@example.com'],
];
}
#[DataProvider('invalidEmailsProvider')]
public function test_rejects_invalid_formats(string $email): void
{
$this->expectException(InvalidArgumentException::class);
new Email($email);
}
public static function invalidEmailsProvider(): array
{
return [
'empty' => [''],
'no at' => ['userexample.com'],
'no domain' => ['user@'],
'spaces' => ['user @example.com'],
];
}
Analyze the class:
Determine test cases:
Generate test class:
src/Domain/Order/Order.php → tests/Unit/Domain/Order/OrderTest.php#[Group('unit')], #[CoversClass]Generate test methods:
Add helpers if needed:
// Value comparisons
self::assertSame($expected, $actual); // ===
self::assertEquals($expected, $actual); // ==
self::assertTrue($condition);
self::assertFalse($condition);
self::assertNull($value);
self::assertNotNull($value);
// Types
self::assertInstanceOf(ClassName::class, $object);
// Strings
self::assertStringContainsString($needle, $haystack);
self::assertStringStartsWith($prefix, $string);
// Arrays
self::assertCount($expected, $array);
self::assertContains($needle, $array);
self::assertArrayHasKey($key, $array);
// Exceptions
$this->expectException(ExceptionClass::class);
$this->expectExceptionMessage('message');
$this->expectExceptionCode(404);
Provide:
The generator will:
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.