create-integration-test

Generates PHPUnit integration tests for PHP 8.4. Creates tests with real dependencies, database transactions, HTTP mocking. Supports repositories, API clients, message handlers.

59 stars

Best use case

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

Generates PHPUnit integration tests for PHP 8.4. Creates tests with real dependencies, database transactions, HTTP mocking. Supports repositories, API clients, message handlers.

Teams using create-integration-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/create-integration-test/SKILL.md --create-dirs "https://raw.githubusercontent.com/dykyi-roman/awesome-claude-code/main/skills/create-integration-test/SKILL.md"

Manual Installation

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

How create-integration-test Compares

Feature / Agentcreate-integration-testStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Generates PHPUnit integration tests for PHP 8.4. Creates tests with real dependencies, database transactions, HTTP mocking. Supports repositories, API clients, message handlers.

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.

Related Guides

SKILL.md Source

# Integration Test Generator

Generates PHPUnit 11+ integration tests for PHP 8.4 infrastructure components.

## Characteristics

- **Real dependencies** — uses actual implementations
- **Database transactions** — rollback after each test
- **Isolated** — no cross-test contamination
- **Slower than unit** — acceptable for infrastructure testing

## Template

```php
<?php

declare(strict_types=1);

namespace Tests\Integration\{Namespace};

use {FullyQualifiedClassName};
use PHPUnit\Framework\Attributes\Group;
use Tests\IntegrationTestCase;

#[Group('integration')]
final class {ClassName}Test extends IntegrationTestCase
{
    private {ClassName} $sut;

    protected function setUp(): void
    {
        parent::setUp();
        $this->sut = $this->getContainer()->get({ClassName}::class);
        $this->beginTransaction();
    }

    protected function tearDown(): void
    {
        $this->rollbackTransaction();
        parent::tearDown();
    }

    public function test_{operation}_{scenario}(): void
    {
        // Arrange
        {arrange_code}

        // Act
        {act_code}

        // Assert
        {assert_code}
    }
}
```

## Base Test Case

```php
<?php

declare(strict_types=1);

namespace Tests;

use Doctrine\DBAL\Connection;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;

abstract class IntegrationTestCase extends TestCase
{
    private static ?ContainerInterface $container = null;
    private ?Connection $connection = null;

    protected function getContainer(): ContainerInterface
    {
        if (self::$container === null) {
            self::$container = require __DIR__ . '/../config/container.php';
        }
        return self::$container;
    }

    protected function beginTransaction(): void
    {
        $this->connection = $this->getContainer()->get(Connection::class);
        $this->connection->beginTransaction();
    }

    protected function rollbackTransaction(): void
    {
        $this->connection?->rollBack();
    }

    protected function getConnection(): Connection
    {
        return $this->connection ?? $this->getContainer()->get(Connection::class);
    }
}
```

## Test Patterns by Component

### Repository Tests

```php
#[Group('integration')]
final class DoctrineOrderRepositoryTest extends IntegrationTestCase
{
    private OrderRepositoryInterface $repository;

    protected function setUp(): void
    {
        parent::setUp();
        $this->repository = $this->getContainer()->get(OrderRepositoryInterface::class);
        $this->beginTransaction();
    }

    protected function tearDown(): void
    {
        $this->rollbackTransaction();
        parent::tearDown();
    }

    // Save and retrieve
    public function test_saves_and_retrieves_order(): void
    {
        $order = OrderMother::pending();

        $this->repository->save($order);
        $found = $this->repository->findById($order->id());

        self::assertNotNull($found);
        self::assertTrue($order->id()->equals($found->id()));
    }

    // Update existing
    public function test_updates_existing_order(): void
    {
        $order = OrderMother::pending();
        $this->repository->save($order);

        $order->addItem(ProductMother::book(), 1);
        $order->confirm();
        $this->repository->save($order);

        $found = $this->repository->findById($order->id());
        self::assertTrue($found->isConfirmed());
    }

    // Delete
    public function test_deletes_order(): void
    {
        $order = OrderMother::pending();
        $this->repository->save($order);

        $this->repository->delete($order);

        $found = $this->repository->findById($order->id());
        self::assertNull($found);
    }

    // Not found
    public function test_returns_null_for_nonexistent(): void
    {
        $result = $this->repository->findById(OrderId::generate());

        self::assertNull($result);
    }

    // Query methods
    public function test_finds_orders_by_customer(): void
    {
        $customerId = CustomerId::generate();
        $order1 = OrderMother::forCustomer($customerId);
        $order2 = OrderMother::forCustomer($customerId);
        $order3 = OrderMother::forCustomer(CustomerId::generate());
        $this->repository->save($order1);
        $this->repository->save($order2);
        $this->repository->save($order3);

        $orders = $this->repository->findByCustomer($customerId);

        self::assertCount(2, $orders);
    }

    // Pagination
    public function test_paginates_results(): void
    {
        for ($i = 0; $i < 25; $i++) {
            $this->repository->save(OrderMother::pending());
        }

        $page1 = $this->repository->findAll(limit: 10, offset: 0);
        $page2 = $this->repository->findAll(limit: 10, offset: 10);
        $page3 = $this->repository->findAll(limit: 10, offset: 20);

        self::assertCount(10, $page1);
        self::assertCount(10, $page2);
        self::assertCount(5, $page3);
    }
}
```

### HTTP Client Tests

```php
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;

#[Group('integration')]
final class StripePaymentGatewayTest extends IntegrationTestCase
{
    public function test_charges_card_successfully(): void
    {
        // Arrange
        $mockResponse = new MockResponse(json_encode([
            'id' => 'ch_123',
            'status' => 'succeeded',
            'amount' => 1000,
        ]), ['http_code' => 200]);
        $httpClient = new MockHttpClient($mockResponse);
        $gateway = new StripePaymentGateway($httpClient, 'sk_test_xxx');

        // Act
        $result = $gateway->charge(
            Money::USD(1000),
            new CardToken('tok_visa')
        );

        // Assert
        self::assertTrue($result->isSuccessful());
        self::assertSame('ch_123', $result->transactionId());
    }

    public function test_handles_declined_card(): void
    {
        // Arrange
        $mockResponse = new MockResponse(json_encode([
            'error' => [
                'type' => 'card_error',
                'code' => 'card_declined',
            ],
        ]), ['http_code' => 402]);
        $httpClient = new MockHttpClient($mockResponse);
        $gateway = new StripePaymentGateway($httpClient, 'sk_test_xxx');

        // Act
        $result = $gateway->charge(
            Money::USD(1000),
            new CardToken('tok_chargeDeclined')
        );

        // Assert
        self::assertFalse($result->isSuccessful());
        self::assertSame('card_declined', $result->errorCode());
    }

    public function test_handles_network_error(): void
    {
        // Arrange
        $mockResponse = new MockResponse('', ['error' => 'Connection refused']);
        $httpClient = new MockHttpClient($mockResponse);
        $gateway = new StripePaymentGateway($httpClient, 'sk_test_xxx');

        // Assert
        $this->expectException(PaymentGatewayException::class);

        // Act
        $gateway->charge(Money::USD(1000), new CardToken('tok_visa'));
    }
}
```

### Message Handler Tests

```php
#[Group('integration')]
final class SendOrderConfirmationHandlerTest extends IntegrationTestCase
{
    private SendOrderConfirmationHandler $handler;
    private InMemoryMailer $mailer;

    protected function setUp(): void
    {
        parent::setUp();
        $this->mailer = new InMemoryMailer();
        $this->handler = new SendOrderConfirmationHandler(
            $this->getContainer()->get(OrderRepositoryInterface::class),
            $this->mailer
        );
        $this->beginTransaction();
    }

    protected function tearDown(): void
    {
        $this->rollbackTransaction();
        parent::tearDown();
    }

    public function test_sends_confirmation_email(): void
    {
        // Arrange
        $order = OrderMother::confirmed();
        $this->getRepository()->save($order);
        $message = new SendOrderConfirmation($order->id()->toString());

        // Act
        $this->handler->__invoke($message);

        // Assert
        $sentEmails = $this->mailer->getSentEmails();
        self::assertCount(1, $sentEmails);
        self::assertSame($order->customerEmail()->value, $sentEmails[0]->to);
    }

    public function test_throws_for_nonexistent_order(): void
    {
        // Arrange
        $message = new SendOrderConfirmation('nonexistent-id');

        // Assert
        $this->expectException(OrderNotFoundException::class);

        // Act
        $this->handler->__invoke($message);
    }
}
```

### Cache Tests

```php
#[Group('integration')]
final class RedisCacheAdapterTest extends IntegrationTestCase
{
    private RedisCacheAdapter $cache;

    protected function setUp(): void
    {
        parent::setUp();
        $redis = $this->getContainer()->get(Redis::class);
        $redis->flushDb();
        $this->cache = new RedisCacheAdapter($redis);
    }

    public function test_stores_and_retrieves_value(): void
    {
        $this->cache->set('key', 'value', 60);

        $result = $this->cache->get('key');

        self::assertSame('value', $result);
    }

    public function test_returns_null_for_missing_key(): void
    {
        $result = $this->cache->get('nonexistent');

        self::assertNull($result);
    }

    public function test_returns_default_for_missing_key(): void
    {
        $result = $this->cache->get('nonexistent', 'default');

        self::assertSame('default', $result);
    }

    public function test_deletes_key(): void
    {
        $this->cache->set('key', 'value', 60);

        $this->cache->delete('key');

        self::assertNull($this->cache->get('key'));
    }

    public function test_expires_after_ttl(): void
    {
        $this->cache->set('key', 'value', 1);

        sleep(2);

        self::assertNull($this->cache->get('key'));
    }
}
```

## Database Setup Options

### SQLite In-Memory

```xml
<!-- phpunit.xml -->
<php>
    <env name="DATABASE_URL" value="sqlite:///:memory:"/>
</php>
```

### Testcontainers

```php
use Testcontainers\Container\PostgreSqlContainer;

abstract class DatabaseTestCase extends TestCase
{
    protected static ?PostgreSqlContainer $postgres = null;

    public static function setUpBeforeClass(): void
    {
        self::$postgres = PostgreSqlContainer::make('15.0')
            ->withDatabase('test_db')
            ->start();
    }

    public static function tearDownAfterClass(): void
    {
        self::$postgres?->stop();
    }

    protected function getDsn(): string
    {
        return self::$postgres->getDsn();
    }
}
```

## Generation Instructions

1. **Identify component type:**
   - Repository → Database tests
   - HTTP Client → Mock HTTP responses
   - Message Handler → In-memory transport
   - Cache → Redis/in-memory tests

2. **Determine test cases:**
   - CRUD operations
   - Query methods
   - Error handling
   - Edge cases (not found, duplicates)

3. **Generate base test case if needed:**
   - Transaction management
   - Container access
   - Cleanup utilities

4. **Generate test class:**
   - Match namespace structure
   - Add `#[Group('integration')]`
   - Setup/teardown with transactions

5. **Add test data helpers:**
   - Use existing Mothers/Builders
   - Create fixtures for complex scenarios

## Usage

Provide:
- Path to infrastructure class
- Database type (SQLite/PostgreSQL/MySQL)
- External services to mock (optional)

The generator will:
1. Analyze the infrastructure class
2. Determine appropriate test patterns
3. Generate comprehensive integration tests
4. Include transaction management

Related Skills

testing-knowledge

59
from dykyi-roman/awesome-claude-code

Testing knowledge base for PHP 8.4 projects. Provides testing pyramid, AAA pattern, naming conventions, isolation principles, DDD testing guidelines, and PHPUnit patterns.

suggest-testability-improvements

59
from dykyi-roman/awesome-claude-code

Suggests testability improvements for PHP code. Provides DI refactoring suggestions, mock opportunities, interface extraction, testing strategy recommendations.

detect-test-smells

59
from dykyi-roman/awesome-claude-code

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.

create-visitor

59
from dykyi-roman/awesome-claude-code

Generates Visitor pattern for PHP 8.4. Creates operations on object structures without modifying element classes, with visitor interface, concrete visitors, and visitable elements. Includes unit tests.

create-value-object

59
from dykyi-roman/awesome-claude-code

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

create-use-case

59
from dykyi-roman/awesome-claude-code

Generates Application Use Cases for PHP 8.4. Creates orchestration services that coordinate domain objects, handle transactions, and dispatch events. Includes unit tests.

create-unit-test

59
from dykyi-roman/awesome-claude-code

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.

create-unit-of-work

59
from dykyi-roman/awesome-claude-code

Generates Unit of Work pattern components for PHP 8.4. Creates transactional consistency infrastructure with aggregate tracking, flush/rollback, domain event collection, and unit tests.

create-timeout

59
from dykyi-roman/awesome-claude-code

Generates Timeout pattern components for PHP 8.4. Creates execution time limit infrastructure with configurable timeouts, fallback support, stream timeouts, and unit tests.

create-test-double

59
from dykyi-roman/awesome-claude-code

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

create-test-builder

59
from dykyi-roman/awesome-claude-code

Generates Test Data Builder and Object Mother patterns for PHP 8.4. Creates fluent builders with sensible defaults and factory methods for test data creation.

create-template-method

59
from dykyi-roman/awesome-claude-code

Generates Template Method pattern for PHP 8.4. Creates abstract algorithm skeleton with customizable steps, allowing subclasses to override specific parts without changing structure. Includes unit tests.