diff --git a/PhpUnit/AbstractContainerBuilderTestCase.php b/PhpUnit/AbstractContainerBuilderTestCase.php
index a27d8bd..83dbf75 100644
--- a/PhpUnit/AbstractContainerBuilderTestCase.php
+++ b/PhpUnit/AbstractContainerBuilderTestCase.php
@@ -129,6 +129,21 @@ protected function assertContainerBuilderHasAlias($aliasId, $expectedServiceId =
);
}
+ /**
+ * Assert that the ContainerBuilder for this test has a service which is created by other service
+ *
+ * @param $serivceId
+ * @param $expectedFactoryClass
+ * @param $expectedFactoryMethod
+ */
+ protected function assertContainerBuilderHasCreatedByFactoryService($serviceId, $expectedFactoryClass = null, $expectedFactoryMethod)
+ {
+ self::assertThat(
+ $this->container,
+ new ContainerBuilderHasFactoryConstraint($serviceId, $expectedFactoryClass, $expectedFactoryMethod)
+ );
+ }
+
/**
* Assert that the ContainerBuilder for this test has a parameter and that its value is the given value.
*
diff --git a/PhpUnit/ContainerBuilderHasFactoryConstraint.php b/PhpUnit/ContainerBuilderHasFactoryConstraint.php
new file mode 100644
index 0000000..3432da3
--- /dev/null
+++ b/PhpUnit/ContainerBuilderHasFactoryConstraint.php
@@ -0,0 +1,215 @@
+serviceId = $serviceId;
+ $this->expectedFactoryClass = $expectedFactoryClass;
+ $this->expectedFactoryMethod = $expectedFactoryMethod;
+ }
+
+ public function toString()
+ {
+ if (null === $this->expectedFactoryClass) {
+ return sprintf('"%s" has factory', $this->serviceId);
+ }
+
+ return sprintf('"%s" has factory "@%s:%s"', $this->serviceId, $this->expectedFactoryClass, $this->expectedFactoryMethod);
+ }
+
+ public function evaluate($other, $description = '', $returnResult = false)
+ {
+ if (!($other instanceof ContainerBuilder)) {
+ throw new \InvalidArgumentException(
+ 'Expected an instance of Symfony\Component\DependencyInjection\ContainerBuilder'
+ );
+ }
+
+ if (!$this->evaluateServiceId($other, $returnResult)) {
+ return false;
+ }
+
+ if (!$this->evaluateFactory($other, $returnResult)) {
+ return false;
+ }
+
+ if ($this->expectedFactoryClass !== null && !$this->evaluateFactoryClass($other, $returnResult)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private function evaluateServiceId(ContainerBuilder $containerBuilder, $returnResult)
+ {
+ if (!$containerBuilder->hasDefinition($this->serviceId)) {
+ if ($returnResult) {
+ return false;
+ }
+
+ $this->fail(
+ $containerBuilder,
+ sprintf(
+ 'The container builder has no service "%s"',
+ $this->serviceId
+ )
+ );
+ }
+
+ return true;
+ }
+
+ private function evaluateFactory(ContainerBuilder $containerBuilder, $returnResult)
+ {
+ /** @var Definition */
+ $definition = $containerBuilder->getDefinition($this->serviceId);
+
+ $factory = $this->getFactoryData($definition);
+
+ if (!is_array($factory)) {
+ if ($returnResult) {
+ return false;
+ }
+
+ $this->fail(
+ $containerBuilder,
+ sprintf(
+ 'The container builder has service "%s" with not "%s" factory',
+ $this->serviceId,
+ $this->expectedFactoryClass
+ )
+ );
+ }
+
+ return true;
+ }
+
+ private function evaluateFactoryClass(ContainerBuilder $containerBuilder, $returnResult)
+ {
+ /** @var Definition */
+ $definition = $containerBuilder->getDefinition($this->serviceId);
+
+ $factory = $this->getFactoryData($definition);
+
+ list($factoryDefinition, $factoryMethod) = $factory;
+
+ if ($factoryDefinition instanceof Reference) {
+ $factoryClass = (string)$factoryDefinition;
+ } elseif (is_string($factoryDefinition)) {
+ $factoryClass = $factoryDefinition;
+ } else {
+ if ($returnResult) {
+ return false;
+ }
+
+ $this->fail(
+ $containerBuilder,
+ sprintf(
+ 'The container builder has service "%s" with not service "%s" factory',
+ $this->serviceId,
+ $this->expectedFactoryClass
+ )
+ );
+ }
+
+ $constraint = new IsEqual($this->expectedFactoryClass);
+ if (!$constraint->evaluate($factoryClass, '', true)) {
+ if ($returnResult) {
+ return false;
+ }
+
+ $this->fail(
+ $containerBuilder,
+ sprintf(
+ 'The container builder has service "%s" with not service class "%s" factory',
+ $this->serviceId,
+ $this->expectedFactoryClass
+ )
+ );
+ }
+
+ if ($this->expectedFactoryMethod) {
+ $constraint = new IsEqual($this->expectedFactoryMethod);
+ if (!$constraint->evaluate($factoryMethod, '', true)) {
+ if ($returnResult) {
+ return false;
+ }
+
+ $this->fail(
+ $containerBuilder,
+ sprintf(
+ 'The container builder has service "%s" with not service class method "%s::%s" factory',
+ $this->serviceId,
+ $this->expectedFactoryClass,
+ $this->expectedFactoryMethod
+ )
+ );
+ }
+ }
+
+ return true;
+ }
+
+ private function getFactoryData(Definition $definition)
+ {
+ if (self::isLegacySymfonyDI()) {
+ $factoryService = $definition->getFactoryService();
+ $factoryMethod = $definition->getFactoryMethod();
+ $factoryClass = $definition->getFactoryClass();
+ if (!$factoryService && !$factoryClass) {
+ return null;
+ }
+
+ return array( $factoryClass ? $factoryClass : $factoryService, $factoryMethod );
+ } else {
+ $factory = $definition->getFactory();
+ if (is_array($factory)) {
+ return $factory;
+ }
+
+ if (is_string($factory) && false !== strpos($factory, ':')) {
+ return preg_split('/:/', $factory, 2);
+ }
+
+ return $factory;
+ }
+ }
+
+
+ public static function isLegacySymfonyDI()
+ {
+ return !method_exists(Definition::class, 'getFactory') &&
+ method_exists(Definition::class, 'getFactoryService');
+ }
+}
diff --git a/README.md b/README.md
index db8f41d..0ce36d9 100644
--- a/README.md
+++ b/README.md
@@ -301,6 +301,10 @@ These are the available semantic assertions for each of the test cases shown abo
Assert that the ContainerBuilder
for this test has an alias.
assertContainerBuilderHasAlias($aliasId, $expectedServiceId)
Assert that the ContainerBuilder
for this test has an alias and that it is an alias for the given service id.
+assertContainerBuilderHasCreatedByFactoryService($serviceId)
+Assert that the ContainerBuilder
for this test has an service with factory.
+assertContainerBuilderHasCreatedByFactoryService($serviceId,$factoryService, $factoryMethod)
+Assert that the ContainerBuilder
for this test has an service with factory for given factory service and method.
assertContainerBuilderHasParameter($parameterName)
Assert that the ContainerBuilder
for this test has a parameter.
assertContainerBuilderHasParameter($parameterName, $expectedParameterValue)
diff --git a/Tests/Fixtures/MatthiasDependencyInjectionTestExtension.php b/Tests/Fixtures/MatthiasDependencyInjectionTestExtension.php
index ea13f15..6a315be 100644
--- a/Tests/Fixtures/MatthiasDependencyInjectionTestExtension.php
+++ b/Tests/Fixtures/MatthiasDependencyInjectionTestExtension.php
@@ -5,8 +5,11 @@
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
+use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
+use Matthias\SymfonyDependencyInjectionTest\PhpUnit\ContainerBuilderHasFactoryConstraint;
class MatthiasDependencyInjectionTestExtension implements ExtensionInterface
{
@@ -16,6 +19,17 @@ public function load(array $config, ContainerBuilder $container)
$loader = new XmlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.xml');
+ // load factory services definitions
+ if (ContainerBuilderHasFactoryConstraint::isLegacySymfonyDI()) {
+ $loader->load('services-factory-legacy.xml');
+ } else {
+ $loader->load('services-factory.xml');
+
+ // Load old syntax for services in YML files
+ $ymlLoader = new YamlFileLoader($container, new FileLocator(__DIR__));
+ $ymlLoader->load('services-factory-old-syntax.yml');
+ }
+
// set a parameter manually
$container->setParameter('manual_parameter', 'parameter value');
@@ -31,6 +45,22 @@ public function load(array $config, ContainerBuilder $container)
// add an alias to an existing service
$container->setAlias('manual_alias', 'service_id');
+
+ // add an factory service
+ $container->register('manual_factory_service', 'stdClass');
+
+ if (ContainerBuilderHasFactoryConstraint::isLegacySymfonyDI()) {
+ $container
+ ->register('manual_created_by_factory_service', 'stdClass')
+ ->setFactoryService(new Reference('manual_factory_service'))
+ ->setFactoryMethod('factoryMethod');
+ ;
+ } else {
+ $container
+ ->register('manual_created_by_factory_service', 'stdClass')
+ ->setFactory('manual_factory_service:factoryMethod')
+ ;
+ }
}
public function getAlias()
diff --git a/Tests/Fixtures/services-factory-legacy.xml b/Tests/Fixtures/services-factory-legacy.xml
new file mode 100644
index 0000000..736cb9e
--- /dev/null
+++ b/Tests/Fixtures/services-factory-legacy.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/Fixtures/services-factory-old-syntax.yml b/Tests/Fixtures/services-factory-old-syntax.yml
new file mode 100644
index 0000000..7544846
--- /dev/null
+++ b/Tests/Fixtures/services-factory-old-syntax.yml
@@ -0,0 +1,3 @@
+services:
+ created_with_factory_with_old_syntax:
+ factory: ['@factory_service', 'factoryMethod']
diff --git a/Tests/Fixtures/services-factory.xml b/Tests/Fixtures/services-factory.xml
new file mode 100644
index 0000000..5037246
--- /dev/null
+++ b/Tests/Fixtures/services-factory.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tests/Fixtures/services.xml b/Tests/Fixtures/services.xml
index 9abc74b..84a5ce4 100644
--- a/Tests/Fixtures/services.xml
+++ b/Tests/Fixtures/services.xml
@@ -26,5 +26,6 @@
+
diff --git a/Tests/PhpUnit/AbstractExtensionTestCaseTest.php b/Tests/PhpUnit/AbstractExtensionTestCaseTest.php
index f24215d..9475b1d 100644
--- a/Tests/PhpUnit/AbstractExtensionTestCaseTest.php
+++ b/Tests/PhpUnit/AbstractExtensionTestCaseTest.php
@@ -3,6 +3,7 @@
namespace Matthias\DependencyInjectionTests\Test\DependencyInjection;
use Matthias\SymfonyDependencyInjectionTest\PhpUnit\AbstractExtensionTestCase;
+use Matthias\SymfonyDependencyInjectionTest\PhpUnit\ContainerBuilderHasFactoryConstraint;
use Matthias\SymfonyDependencyInjectionTest\Tests\Fixtures\MatthiasDependencyInjectionTestExtension;
use PHPUnit\Framework\ExpectationFailedException;
@@ -28,6 +29,9 @@ public function if_load_is_successful_it_does_not_fail()
// defined in services.xml
$this->assertContainerBuilderHasSyntheticService('synthetic_service');
+ // defined in services-factory.xml
+ $this->assertContainerBuilderHasCreatedByFactoryService('created_by_factory_service', 'factory_service', 'factoryMethod');
+
// manually defined parameter
$this->assertContainerBuilderHasParameter('manual_parameter', 'parameter value');
// Just check parameter exists, value will not be checked.
@@ -49,8 +53,24 @@ public function if_load_is_successful_it_does_not_fail()
// check for existence of manually created arguments, not checking values.
$this->assertContainerBuilderHasServiceDefinitionWithArgument('manual_service_id', 0);
$this->assertContainerBuilderHasServiceDefinitionWithArgument('manual_service_id', 1);
+
+ // manually created factory service
+ $this->assertContainerBuilderHasCreatedByFactoryService(
+ 'manual_created_by_factory_service',
+ 'manual_factory_service',
+ 'factoryMethod'
+ );
+
+ if (!ContainerBuilderHasFactoryConstraint::isLegacySymfonyDI()) {
+ $this->assertContainerBuilderHasCreatedByFactoryService(
+ 'created_with_factory_with_old_syntax',
+ 'factory_service',
+ 'factoryMethod'
+ );
+ }
}
+
/**
* @test
*/
diff --git a/Tests/PhpUnit/ContainerBuilderHasFactoryConstraintTest.php b/Tests/PhpUnit/ContainerBuilderHasFactoryConstraintTest.php
new file mode 100644
index 0000000..6f7b0b9
--- /dev/null
+++ b/Tests/PhpUnit/ContainerBuilderHasFactoryConstraintTest.php
@@ -0,0 +1,131 @@
+assertSame($shouldMatch, $constraint->evaluate($containerBuilder, null, true));
+ }
+
+ public function containerBuilderProvider()
+ {
+ $rightServiceId = 'some_service_id';
+ $wrongServiceId = 'other_service_id';
+ $factoryClass = 'factory_class_service';
+ $invalidFactoryClass = 'invalid_class_service';
+ $factoryMethod = 'someMethod';
+ $invalidFactoryMethod = 'invalidMethod';
+
+ $emptyContainerBuilder = new ContainerBuilder();
+
+ $builderWithFactory = new ContainerBuilder();
+ $factoryReference = new Reference($factoryClass);
+
+ if (ContainerBuilderHasFactoryConstraint::isLegacySymfonyDI()) {
+ $builderWithFactory->register($rightServiceId)
+ ->setFactoryService($factoryReference)
+ ->setFactoryMethod($factoryMethod);
+ } else {
+ $builderWithFactory->register($rightServiceId)
+ ->setFactory([$factoryReference,$factoryMethod]);
+ }
+
+ $builderWithFactory->register($wrongServiceId);
+
+ return array(
+ // the container does not have the service
+ array($emptyContainerBuilder, $rightServiceId, null, null, false),
+
+ // the container has service created by factory
+ array($builderWithFactory, $rightServiceId, null, null, true ),
+
+ // the container has service, but they has not factory
+ array($builderWithFactory, $wrongServiceId, null, null, false ),
+
+ // the container has service created by factory, but factory is invalid
+ array($builderWithFactory, $rightServiceId, $invalidFactoryClass, $factoryMethod, false ),
+
+ // the container has service created by factory, but factory method is invalid
+ array($builderWithFactory, $rightServiceId, $factoryClass, $invalidFactoryMethod, false ),
+
+ // the container has service created by factory, and whole arguments are valid
+ array($builderWithFactory, $rightServiceId, $factoryClass, $factoryMethod, true ),
+
+ // the container has service created by factory, and whole arguments are valid
+ array($builderWithFactory, $wrongServiceId, $factoryClass, $factoryMethod, false ),
+
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function it_has_a_string_representation()
+ {
+ $serviceId = 'service_id';
+ $factoryClass = 'SomeFactoryClass';
+ $factoryMethod = 'someMethod';
+ $constraint = new ContainerBuilderHasFactoryConstraint($serviceId, $factoryClass, $factoryMethod);
+ $this->assertSame(
+ '"service_id" has factory "@SomeFactoryClass:someMethod"',
+ $constraint->toString()
+ );
+ }
+
+ /**
+ * @test
+ */
+ public function it_expects_a_string_for_service_id()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('string');
+
+ new ContainerBuilderHasFactoryConstraint(new \stdClass(), 'service_id');
+ }
+
+ /**
+ * @test
+ */
+ public function it_expects_a_string_for_factory_class()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('string');
+
+ new ContainerBuilderHasFactoryConstraint('service_id', new \stdClass(), '');
+ }
+
+ /**
+ * @test
+ */
+ public function it_expects_a_string_for_factory_method()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('string');
+
+ new ContainerBuilderHasFactoryConstraint('service_id', 'FactoryClass', new \stdClass());
+ }
+
+ /**
+ * @test
+ */
+ public function it_expects_factory_class_be_informed()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('inform');
+
+ new ContainerBuilderHasFactoryConstraint('service_id', null, 'factoryMethod');
+ }
+}