| name | acc-create-unit-test |
| description | Generates PHPUnit unit tests for PHP 8.5. Creates isolated tests with AAA pattern, proper naming, attributes, and one behavior per test. Supports Value Objects, Entities, Services. |
Unit Test Generator
Generates PHPUnit 11+ unit tests for PHP 8.5 classes.
Characteristics
- Isolated — no external dependencies (DB, HTTP, filesystem)
- Fast — executes in <100ms
- Focused — one behavior per test
- AAA Pattern — Arrange-Act-Assert structure
- Self-documenting — descriptive test names
Template
<?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}();
}
public function test_{method}_{scenario}_{expected}(): void
{
{arrange_code}
{act_code}
{assert_code}
}
}
Naming Convention
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
Test Patterns by Component
Value Object Tests
#[Group('unit')]
#[CoversClass(Email::class)]
final class EmailTest extends TestCase
{
public function test_creates_with_valid_email(): void
{
$email = new Email('user@example.com');
self::assertSame('user@example.com', $email->value);
}
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 ();
}
{
= ();
= ();
::(->());
}
{
= ();
= ();
::(->());
}
}
Entity Tests
#[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')
);
}
public function test_has_unique_identity(): void
{
self::assertSame('order-123', $this->order->id()->toString());
}
public function test_is_pending_when_created(): void
{
self::assertTrue($this->order->());
}
{
->order->(::(), );
->order->();
::(->order->());
}
{
->order->(::(), );
->order->();
->(::);
->order->();
}
{
->order->(::(::()), );
::(::(), ->order->());
}
{
->order->(::(), );
->order->();
= ->order->();
::(, );
::(::, []);
}
}
Domain Service Tests
#[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
{
$source = AccountMother::withBalance(Money::EUR(1000));
$target = ::(::());
->repository->();
->repository->();
->service->(
->(),
->(),
::()
);
= ->repository->(->());
= ->repository->(->());
::(::(), ->());
::(::(), ->());
}
{
= ::(::());
= ::(::());
->repository->();
->repository->();
->(::);
->service->(
->(),
->(),
::()
);
}
}
Data Providers
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::);
();
}
{
[
=> [],
=> [],
=> [],
=> [],
];
}
Generation Instructions
-
Analyze the class:
- Identify public methods
- Identify dependencies (constructor parameters)
- Identify value objects (final readonly)
- Identify entities (has id, state changes)
- Identify services (orchestrates, uses repositories)
-
Determine test cases:
- Happy path for each method
- Edge cases (null, empty, boundary)
- Exception paths (validation failures)
- State transitions (for entities)
-
Generate test class:
- Match namespace:
src/Domain/Order/Order.php → tests/Unit/Domain/Order/OrderTest.php
- Add attributes:
#[Group('unit')], #[CoversClass]
- Create setUp if shared state needed
-
Generate test methods:
- Follow naming convention
- Use AAA structure
- One assertion group per test
-
Add helpers if needed:
- Use existing Mothers/Builders
- Create inline builders for simple cases
Assertions Reference
self::assertSame($expected, $actual);
self::assertEquals($expected, $actual);
self::assertTrue($condition);
self::assertFalse($condition);
self::assertNull($value);
self::assertNotNull($value);
self::assertInstanceOf(ClassName::class, $object);
self::assertStringContainsString($needle, $haystack);
self::assertStringStartsWith($prefix, $string);
self::assertCount($expected, $array);
self::assertContains($needle, $array);
self::assertArrayHasKey($key, );
->(::);
->();
->();
Usage
Provide:
- Path to class to test
- Or class name and namespace
- Specific methods to focus on (optional)
The generator will:
- Read the source class
- Analyze methods and dependencies
- Generate comprehensive test class
- Include happy path + edge cases + exceptions