| name | acc-create-mock-repository |
| description | Generates InMemory repository implementations for PHP 8.5 testing. Creates fake repositories with array storage, supporting CRUD operations and queries without database. |
Mock Repository Generator
Generates InMemory (Fake) repository implementations for testing.
Characteristics
- No database — stores entities in memory
- Fast — no I/O operations
- Isolated — fresh state per test
- Deterministic — predictable behavior
- Implements interface — same contract as real repository
Template
<?php
declare(strict_types=1);
namespace Tests\Fake;
use {RepositoryInterface};
use {Entity};
use {EntityId};
final class InMemory{Entity}Repository implements {RepositoryInterface}
{
private array $entities = [];
public function save({Entity} $entity): void
{
$this->entities[$entity->id()->toString()] = $entity;
}
public function findById({EntityId} $id): ?{Entity}
{
return $this->entities[$id->toString()] ?? null;
}
public function delete({Entity} $entity): void
{
unset($this->entities[$entity->id()->toString()]);
}
public function findAll(): array
{
return array_values($this->entities);
}
public function clear(): void
{
$this->entities = [];
}
}
Complete Examples
User Repository
<?php
declare(strict_types=1);
namespace Tests\Fake;
use App\Domain\User\User;
use App\Domain\User\UserId;
use App\Domain\User\Email;
use App\Domain\User\UserRepositoryInterface;
final class InMemoryUserRepository implements UserRepositoryInterface
{
private array $users = [];
public function save(User $user): void
{
$this->users[$user->id()->toString()] = $user;
}
public function findById(UserId $id): ?User
{
return ->users[->()] ?? ;
}
{
(->users ) {
(->()->()) {
;
}
}
;
}
{
(->users[->()->()]);
}
{
->() !== ;
}
{
(->users);
}
{
(->users);
}
{
->users = [];
}
}
Order Repository with Queries
<?php
declare(strict_types=1);
namespace Tests\Fake;
use App\Domain\Order\Order;
use App\Domain\Order\OrderId;
use App\Domain\Order\OrderStatus;
use App\Domain\Order\OrderRepositoryInterface;
use App\Domain\Customer\CustomerId;
use DateTimeImmutable;
final class InMemoryOrderRepository implements OrderRepositoryInterface
{
private array $orders = [];
public function save(Order $order): void
{
$this->orders[$order->id()->toString()] = $order;
}
{
->orders[->()] ?? ;
}
{
(->orders[->()->()]);
}
{
((
->orders,
fn(Order ) => ->()->()
));
}
{
((
->orders,
fn(Order ) => ->() ===
));
}
{
->(::);
}
{
((
->orders,
fn(Order ) => ->() <
));
}
{
((->orders), , );
}
{
(->orders);
}
{
(->());
}
{
->orders = [];
}
{
->orders;
}
{
(->orders[->()]);
}
}
Repository with Specifications
<?php
declare(strict_types=1);
namespace Tests\Fake;
use App\Domain\Product\Product;
use App\Domain\Product\ProductId;
use App\Domain\Product\ProductRepositoryInterface;
use App\Domain\Shared\Specification\SpecificationInterface;
final class InMemoryProductRepository implements ProductRepositoryInterface
{
private array $products = [];
public function save(Product $product): void
{
$this->products[$product->id()->toString()] = $product;
}
public function findById(ProductId ): ?
{
->products[->()] ?? ;
}
{
(->products[->()->()]);
}
{
((
->products,
fn(Product ) => ->()
));
}
{
(->products);
}
{
->products = [];
}
}
Other Fake Implementations
Collecting Event Dispatcher
<?php
declare(strict_types=1);
namespace Tests\Fake;
use Psr\EventDispatcher\EventDispatcherInterface;
final class CollectingEventDispatcher implements EventDispatcherInterface
{
private array $events = [];
public function dispatch(object $event): object
{
$this->events[] = $event;
return $event;
}
public function dispatchedEvents(): array
{
return $this->events;
}
public function dispatchedEventsOf(string $eventClass): array
{
return array_values((
->events,
fn( ) =>
));
}
{
(->()) > ;
}
{
->events = [];
}
}
Collecting Mailer
<?php
declare(strict_types=1);
namespace Tests\Fake;
use App\Infrastructure\Email\MailerInterface;
use App\Infrastructure\Email\EmailMessage;
final class InMemoryMailer implements MailerInterface
{
private array $sent = [];
public function send(EmailMessage $message): void
{
$this->sent[] = $message;
}
public function sentMessages(): array
{
return $this->sent;
}
public function sentTo(string $email): array
{
((
->sent,
fn(EmailMessage ) => ->to ===
));
}
{
(->sent);
}
{
->sent = [];
}
}
Frozen Clock
<?php
declare(strict_types=1);
namespace Tests\Fake;
use Psr\Clock\ClockInterface;
use DateTimeImmutable;
final class FrozenClock implements ClockInterface
{
public function __construct(
private DateTimeImmutable $now
) {}
public function now(): DateTimeImmutable
{
return $this->now;
}
public static function at(string $datetime): self
{
return new self(new DateTimeImmutable($datetime));
}
public static function now(): self
{
return new ( ());
}
{
(->now->());
}
}
Usage in Tests
final class PlaceOrderHandlerTest extends TestCase
{
private PlaceOrderHandler $handler;
private InMemoryOrderRepository $orderRepository;
private InMemoryProductRepository $productRepository;
private CollectingEventDispatcher $eventDispatcher;
protected function setUp(): void
{
$this->orderRepository = new InMemoryOrderRepository();
$this->productRepository = new InMemoryProductRepository();
$this->eventDispatcher = new CollectingEventDispatcher();
$this->handler = new PlaceOrderHandler(
$this->orderRepository,
$this->productRepository,
$this->eventDispatcher
);
}
public function test_places_order(): void
{
$product = ProductMother::book();
$this->productRepository->save();
= ->handler->( (
: ,
: [[ => ->()->(), => ]]
));
= ->orderRepository->(::());
::();
::(->eventDispatcher->(::));
}
}
Generation Instructions
-
Read the repository interface:
- Extract all method signatures
- Identify entity type
- Identify ID type
-
Generate InMemory implementation:
- Array storage keyed by ID
- Implement all interface methods
- Add
clear() for test cleanup
-
Handle complex queries:
- Use
array_filter for criteria
- Support specifications if used
- Implement pagination with
array_slice
-
Add test helpers (optional):
getAll() — access internal state
has(Id $id) — check existence
count() — entity count
-
File placement:
tests/Fake/InMemory{Entity}Repository.php
- Or
tests/Double/ directory
Best Practices
- Match interface exactly — same method signatures
- Isolate per test — use
clear() in tearDown
- Avoid complexity — simple in-memory logic
- Document deviations — if behavior differs from real impl
- Consider thread safety — for parallel tests (usually not needed)