pkly/phpunit-service-create-trait
Laravel/PHP trait that helps you quickly create service instances in PHPUnit tests, reducing boilerplate when setting up dependencies. Handy for service-layer unit tests where you want consistent, reusable test setup.
Installation
composer require --dev pkly/phpunit-service-create-trait
Basic Usage
Add the trait to your test class and use createRealMockedServiceInstance to instantiate services with auto-mocked dependencies:
use PKLY\PHPUnit\ServiceMockHelperTrait;
use PHPUnit\Framework\TestCase;
class MyServiceTest extends TestCase
{
use ServiceMockHelperTrait;
private MyService $service;
protected function setUp(): void
{
$this->service = $this->createRealMockedServiceInstance(MyService::class);
}
public function testDeleteSomething()
{
$mock = $this->createMock(MyEntity::class);
$this->getMockedService(EntityManagerInterface::class)
->expects($this->once())
->method('delete')
->with($mock);
$this->service->deleteSomething($mock);
}
}
Key Files
vendor/pkly/phpunit-service-create-trait/src/ServiceMockHelperTrait.phpAuto-Mocked Dependencies
The trait automatically resolves and mocks constructor dependencies and Symfony’s #[Required] attributes:
// Service with constructor and required method
class MyService {
public function __construct(private Repository $repo) {}
#[Required] public function setLogger(Logger $logger) {}
}
// Test: Dependencies are auto-mocked
$service = $this->createRealMockedServiceInstance(MyService::class);
Custom Dependency Injection Override auto-mocking for specific dependencies:
$customRepo = $this->createMock(Repository::class);
$service = $this->createRealMockedServiceInstance(
MyService::class,
['constructor' => ['repo' => $customRepo]]
);
Partial Mocking
Use createRealPartialMockedServiceInstance for partial mocks:
$partialService = $this->createRealPartialMockedServiceInstance(
MyService::class,
['methods' => ['deleteSomething']]
);
Testing Service Providers
$provider = $this->createRealMockedServiceInstance(MyServiceProvider::class);
$this->assertInstanceOf(MyService::class, $provider->register());
Mocking Eloquent Models
$user = $this->createRealMockedServiceInstance(User::class);
$user->method('getAttribute')->willReturn('test');
Integration with Laravel’s Mockery
Combine with Laravel’s testing helpers:
$mock = Mockery::mock(Repository::class);
$service = $this->createRealMockedServiceInstance(
MyService::class,
['constructor' => ['repo' => $mock]]
);
Testing Jobs/Commands
$job = $this->createRealMockedServiceInstance(MyJob::class);
$job->handle();
Dynamic Dependency Resolution
Use getMockedService() to access auto-mocked dependencies:
$mockedRepo = $this->getMockedService(Repository::class);
$mockedRepo->expects($this->once())->method('find');
Trait Composition Extend the trait for custom logic:
trait CustomServiceTrait {
use ServiceMockHelperTrait;
protected function createServiceWithDefaults(string $class): object {
return $this->createRealMockedServiceInstance($class, [
'constructor' => ['logger' => $this->createMock(Logger::class)]
]);
}
}
Testing Services with Factories
$factory = $this->createRealMockedServiceInstance(PostFactory::class);
$factory->method('create')->willReturn(new Post());
Constructor Parameter Order
TypeError if dependency array order mismatches constructor.constructor array:
$service = $this->createRealMockedServiceInstance(MyService::class, [
'constructor' => ['repo' => $mockRepo, 'logger' => $mockLogger]
]);
Non-Mockable Dependencies
ReflectionException if a dependency cannot be mocked (e.g., DateTime).$service = $this->createRealMockedServiceInstance(MyService::class, [
'constructor' => ['dateTime' => new DateTime()]
]);
Symfony #[Required] Attributes
RuntimeException if a required method is not called.$service = $this->createRealMockedServiceInstance(MyService::class, [
'required' => ['setLogger' => $this->createMock(Logger::class)]
]);
Circular Dependencies
disableOriginalConstructor() for circular cases:
$mock = $this->getMockBuilder(CircularDep::class)
->disableOriginalConstructor()
->getMock();
Inspect Auto-Mocked Dependencies
Use getMockedService() to verify mocks:
$mock = $this->getMockedService(Repository::class);
var_dump($mock->getMockedMethods());
Enable Strict Mocking Configure PHPUnit to fail on unexpected method calls:
$this->getMockedService(Repository::class)
->expects($this->any())
->method('find')
->will($this->throwException(new \RuntimeException('Unexpected call')));
Log Service Creation Override the trait method for debugging:
protected function createRealMockedServiceInstance(string $class, array $options = []): object
{
\Log::debug("Creating {$class} with options: " . json_encode($options));
return parent::createRealMockedServiceInstance($class, $options);
}
Custom Mock Factories Extend the trait to support custom mock creation:
trait CustomMockTrait {
use ServiceMockHelperTrait;
protected function createCustomMock(string $class): object {
return $this->getMockBuilder($class)
->setMethods(['customMethod'])
->getMock();
}
}
Integration with Laravel’s AppServiceProvider
Bind auto-mocked services to the container:
$this->app->bind(MyService::class, function ($app) {
return $this->createRealMockedServiceInstance(MyService::class);
});
Support for Non-Constructor Injection Handle setters or other injection methods:
$service = $this->createRealMockedServiceInstance(MyService::class);
$service->setDependency($this->createMock(Dependency::class));
PHPUnit 10+ Requirement: Ensure your phpunit.xml targets PHPUnit 10+:
<phpunit bootstrap="vendor/autoload.php">
<extensions>
<extension class="PKLY\PHPUnit\ServiceMockHelperTrait"/>
</extensions>
</phpunit>
Autoloading: Run composer dump-autoload after installation if autoloading issues arise.
Trait Naming Conflict: If using another ServiceMockHelperTrait, rename the use statement:
use PKLY\PHPUnit\ServiceMockHelperTrait as ServiceMockTrait;
Eloquent Model Mocking
Service Container Binding
createRealMockedServiceInstance over container bindings in unit tests.Testing Jobs with Queues
$queue = $this->createMock(Queue::class);
$this->app->instance(Queue::class, $queue);
How can I help you explore Laravel packages today?