As far as I understand, Factory and Proxy classes are generated on the fly by the autoloader if they do not exist yet in var/generation (see: What Triggers the Generation of a Factory in Magento 2)
But why do I get this error when referencing a new factory in a unit test?
ReflectionException: Class Magento\Framework\Api\Search\SearchCriteriaBuilderFactory does not exist
[...]/vendor/magento/framework/TestFramework/Unit/Helper/ObjectManager.php:161
use Magento\Framework\Api\Search\SearchCriteriaBuilderFactory;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
class SearchCriteriaTest extends \PHPUnit_Framework_TestCase
{
public function testFactoryGeneration()
{
$searchCriteriaBuilderFactory = (new ObjectManager($this))->getObject(SearchCriteriaBuilderFactory::class);
}
}
I am using the bootstrap file dev/tests/unit/framework/bootstrap.php.
Workarounds I found to generate the class:
using the real object manager (Thanks @DigitalPianism):
\Magento\Framework\App\Bootstrap::create(BP, $_SERVER)->getObjectManager()->create('\Magento\Framework\Api\Search\SearchCriteriaBuilderFactory')run
setup:di:compile(given the factory is referenced in a constructor)
But I still hope to find a clean and performant solution.
Also, not sure if related, but create() of the generated factory from the unit test object manager returns null, so I don't even have a working factory yet.
3 Answers 3
The easiest way to deal with that is to run compilation before running tests:
bin/magento setup:di:compile
The other way is to explicitly define methods for the factory mock eg. instead of doing this:
$someFactoryMock = $this->getMockBuilder('Vendor\Module\Model\SomeFactory')
->disableOriginalConstructor()
->getMock();
Do this:
$someFactoryMock = $this->getMockBuilder('Vendor\Module\Model\SomeFactory')
->disableOriginalConstructor()
->setMethods(['create'])
->getMock();
At some point, I tried to deal with that by calling ObjectManager::getObject before creating mock, but this doesn't look as a clean solution. Another thing is that it didn't help - it created an object, but did not save class in var/generation. I haven't dig into this more.
-
1I accepted this answer because mocking the factory was the most elegant solution and works even if it's not generated.Fabian Schmengler– Fabian Schmengler2016年06月03日 15:34:40 +00:00Commented Jun 3, 2016 at 15:34
The problem originates from PHPUnit mocking library, as it cannot autoload the needed class.
If you take a look into Magento dev repo, it setups Autoloader catcher, that generates a class when it is requested. If you create similar bootstrap file in your module repository it will work quite well: https://github.com/magento/magento2/blob/develop/dev/tests/unit/framework/autoload.php
<?php
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
$autoloader = new \Magento\Framework\TestFramework\Unit\Autoloader\ExtensionGeneratorAutoloader(
new \Magento\Framework\Code\Generator\Io(
new \Magento\Framework\Filesystem\Driver\File(),
TESTS_TEMP_DIR . '/var/generation'
)
);
spl_autoload_register([$autoloader, 'load']);
However I would advise using a different approach, by utilizing a virtual file system, so your materialized generated classes won't break your build if generated classes interface signature changes.
composer require --dev mikey179/vfsStream
And then in your bootstrap file:
$autoloader = new \Magento\Framework\TestFramework\Unit\Autoloader\ExtensionGeneratorAutoloader(
new \Magento\Framework\Code\Generator\Io(
new \Magento\Framework\Filesystem\Driver\File(),
org\bovigo\vfs\vfsStream::setup('my_generated_classes')->url()
)
);
spl_autoload_register([$autoloader, 'load']);
I was using similar approach when created an adapter for PHPSpec https://github.com/EcomDev/phpspec-magento-di-adapter/blob/master/src/Extension.php#L98
-
Sounds great, I'll try it outFabian Schmengler– Fabian Schmengler2016年05月25日 19:23:53 +00:00Commented May 25, 2016 at 19:23
Also you may use something like this
private function getMockupFactory($instanceName)
{
/** Magento\Framework\TestFramework\Unit\Helper\ObjectManager */
$objectManager = $this->objectManagerHelper;
$factory = $this->getMockBuilder($instanceName . 'Factory')
->disableOriginalConstructor()
->setMethods(['create'])
->getMock();
$factory->expects($this->any())
->method('create')
->will($this->returnCallback(function($args) use ($instanceName, $objectManager) {
return $objectManager->getObject($instanceName, $args);
}));
return $factory;
}
and someWhere in code just pass
class Some {
__constructor(
MyFactory $myFactory
){}
}
$this->objectManagerHelper->getObject(Some::class,[
'myFactory' => $this->getMockupFactory(My::class)
])
-
Used a variation of this and it fit my use case perfectly. And I like that it's generic so now I have a go-to any time I need to mock a factory.tbernard– tbernard2018年08月16日 21:13:17 +00:00Commented Aug 16, 2018 at 21:13
-
Nice solution. You might want to change it's behavior a bit so you can have a
$instanceNameand$factoryNamein case you have an interface factory that is expected to return data models.Giel Berkers– Giel Berkers2019年02月05日 09:11:43 +00:00Commented Feb 5, 2019 at 9:11
Explore related questions
See similar questions with these tags.
Magento\Framework\Api\Search\SearchCriteriaBuilder?\Magento\Framework\App\Bootstrap::create(BP, $_SERVER)->getObjectManager()->create('\Magento\Framework\Api\Search\SearchCriteriaBuilderFactory');?getObjectyou callgetBuilder? That should happen directly viagetObjectbut just to test.