acc-create-unit-test

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.

16 stars

Best use case

acc-create-unit-test is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

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.

Teams using acc-create-unit-test should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/acc-create-unit-test/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/testing-security/acc-create-unit-test/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/acc-create-unit-test/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How acc-create-unit-test Compares

Feature / Agentacc-create-unit-testStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

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.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# 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
<?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}
    }
}
```

## 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:**
```php
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

```php
#[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));
    }
}
```

### Entity Tests

```php
#[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]);
    }
}
```

### Domain Service Tests

```php
#[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)
        );
    }
}
```

## Data Providers

```php
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'],
    ];
}
```

## Generation Instructions

1. **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)

2. **Determine test cases:**
   - Happy path for each method
   - Edge cases (null, empty, boundary)
   - Exception paths (validation failures)
   - State transitions (for entities)

3. **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

4. **Generate test methods:**
   - Follow naming convention
   - Use AAA structure
   - One assertion group per test

5. **Add helpers if needed:**
   - Use existing Mothers/Builders
   - Create inline builders for simple cases

## Assertions Reference

```php
// 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);
```

## Usage

Provide:
- Path to class to test
- Or class name and namespace
- Specific methods to focus on (optional)

The generator will:
1. Read the source class
2. Analyze methods and dependencies
3. Generate comprehensive test class
4. Include happy path + edge cases + exceptions

Related Skills

acc-detect-test-smells

16
from diegosouzapw/awesome-omni-skill

Detects test antipatterns and code smells in PHP test suites. Identifies 15 smells (Logic in Test, Mock Overuse, Fragile Tests, Mystery Guest, etc.) with fix recommendations and refactoring patterns for testability.

acc-create-value-object

16
from diegosouzapw/awesome-omni-skill

Generates DDD Value Objects for PHP 8.5. Creates immutable, self-validating objects with equality comparison. Includes unit tests.

acc-create-test-double

16
from diegosouzapw/awesome-omni-skill

Generates test doubles (Mocks, Stubs, Fakes, Spies) for PHP 8.5. Creates appropriate double type based on testing needs with PHPUnit MockBuilder patterns.

acc-create-psr7-http-message

16
from diegosouzapw/awesome-omni-skill

Generates PSR-7 HTTP Message implementations for PHP 8.5. Creates Request, Response, Stream, Uri, and ServerRequest classes with immutability. Includes unit tests.

acc-create-policy

16
from diegosouzapw/awesome-omni-skill

Generates Policy pattern for PHP 8.5. Creates encapsulated business rules for authorization, validation, and domain constraints. Includes unit tests.

acc-create-null-object

16
from diegosouzapw/awesome-omni-skill

Generates Null Object pattern for PHP 8.5. Creates safe default implementations eliminating null checks. Includes unit tests.

acc-create-command

16
from diegosouzapw/awesome-omni-skill

Generates CQRS Commands and Handlers for PHP 8.5. Creates immutable command DTOs with handlers that modify state. Includes unit tests.

acc-analyze-test-coverage

16
from diegosouzapw/awesome-omni-skill

Analyzes PHP codebase for test coverage gaps. Detects untested classes, methods, branches, exception paths, and edge cases. Provides actionable recommendations.

ab-testing

16
from diegosouzapw/awesome-omni-skill

Use when designing experiments for subject lines, offers, cadences, or journeys.

ab-testing-statistician

16
from diegosouzapw/awesome-omni-skill

Expert in statistical analysis for blind A/B and ABX audio testing. Validates randomization, calculates statistical significance, and ensures proper experimental design. Use when implementing A/B test features or analyzing test results.

ab-test-setup

16
from diegosouzapw/awesome-omni-skill

Structured guide for setting up A/B tests with mandatory gates for hypothesis, metrics, and execution readiness.

ab-test-calculator

16
from diegosouzapw/awesome-omni-skill

Calculate statistical significance for A/B tests. Sample size estimation, power analysis, and conversion rate comparisons with confidence intervals.