Skip to content

Instantly share code, notes, and snippets.

@jakzal
Last active July 27, 2018 13:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jakzal/cecbd34ecd88367c8b37b33613ba8abe to your computer and use it in GitHub Desktop.
Save jakzal/cecbd34ecd88367c8b37b33613ba8abe to your computer and use it in GitHub Desktop.
[Idea] Automated injection of test doubles into phpunit test cases
<?php
declare(strict_types=1);
namespace Zalas\Injector\Tests\Service;
use PHPUnit\Framework\TestCase;
use Prophecy\Prophecy\ObjectProphecy;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Zalas\Injector\PHPUnit\TestCase\ServiceContainerTestCase;
use Zalas\Injector\PHPUnit\TestListener\ServiceInjectorListener;
use Zalas\Injector\Service\ContainerFactory;
class MockerTest extends TestCase implements ServiceContainerTestCase
{
/**
* @var ContainerFactory|ContainerInterface|ObjectProphecy
* @mock
* @inject
*/
private $containerFactory;
/**
* @var ContainerInterface|ObjectProphecy
* @mock
* @inject
*/
private $container;
public function test_it_injects_test_doubles()
{
$listener = new ServiceInjectorListener();
$listener->startTest($this);
$this->assertInstanceOf(ObjectProphecy::class, $this->containerFactory);
$this->assertInstanceOf(ObjectProphecy::class, $this->container);
$this->assertInstanceOf(ContainerFactory::class, $this->containerFactory->reveal());
$this->assertInstanceOf(ContainerInterface::class, $this->containerFactory->reveal());
$this->assertInstanceOf(ContainerInterface::class, $this->container->reveal());
}
public function test_it_verifies_test_double_expectations()
{
$listener = new ServiceInjectorListener();
$listener->startTest($this);
$this->containerFactory->create()->willReturn($this->container);
$this->container->reveal()->has('foo');
$this->container->has('foo')->shouldHaveBeenCalled();
}
/**
* @return ContainerInterface
*/
public function createContainer(): ContainerInterface
{
$testDoubler = function ($type): ObjectProphecy {
return $this->prophesize($type);
};
return new class($testDoubler) implements ContainerInterface {
/**
* @var callable
*/
private $testDoubler;
public function __construct(callable $testDoubler)
{
$this->testDoubler = $testDoubler;
}
public function get($id)
{
if (!$this->has($id)) {
throw new class extends \Exception implements NotFoundExceptionInterface {
};
}
$ids = array_filter($this->splitIds($id), function ($id) {
return ObjectProphecy::class !== $id;
});
$prophecy = call_user_func($this->testDoubler, array_shift($ids));
foreach ($ids as $id) {
if (interface_exists($id)) {
$prophecy->willImplement($id);
} else {
$prophecy->willExtend($id);
}
}
return $prophecy;
}
public function has($id)
{
return in_array(ObjectProphecy::class, $this->splitIds($id));
}
private function splitIds($id): array
{
return array_map(function ($id) {
return ltrim($id, '\\');
}, explode('|', $id));
}
};
}
}
@jakzal
Copy link
Author

jakzal commented Jul 27, 2018

The above idea built on top of zalas/phpunit-injector which was created with a different use case in mind.

A slightly different (independent) approach has been implemented in zalas/phpunit-doubles here: https://packagist.org/packages/zalas/phpunit-doubles

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment