Generieren Sie Factory oder Proxy in Unit-Tests: "ReflectionException: Class ... Factory existiert nicht"

8

Soweit ich verstehe, Factoryund ProxyKlassen werden im laufenden Betrieb durch den Autoloader erzeugt , wenn sie nicht existieren noch in var/generation(siehe: Was die Erzeugung einer Fabrik in Magento Trigger 2 )

Aber warum erhalte ich diesen Fehler, wenn ich in einem Komponententest auf eine neue Fabrik verweise?

ReflectionException: Die Klasse Magento \ Framework \ Api \ Search \ SearchCriteriaBuilderFactory ist nicht vorhanden

[...] / 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);
    }
}

Ich benutze die Bootstrap-Datei dev/tests/unit/framework/bootstrap.php.


Problemumgehungen Ich habe festgestellt, dass die Klasse generiert wird:

  • Verwenden des Real Object Managers (Danke @DigitalPianism):

    \Magento\Framework\App\Bootstrap::create(BP, $_SERVER)->getObjectManager()->create('\Magento\Framework\Api\Search\SearchCrite‌​riaBuilderFactory')
  • run setup:di:compile(vorausgesetzt, die Factory wird in einem Konstruktor referenziert)

Aber ich hoffe immer noch, eine saubere und performante Lösung zu finden.

Ich bin mir auch nicht sicher, ob es verwandt ist, aber create()die vom Unit Test Object Manager generierte Factory kehrt zurück null, sodass ich noch nicht einmal eine funktionierende Factory habe.

Fabian Schmengler
quelle
Gute Frage in der Tat. Passiert das mit anderen Klassen oder nur mit Magento\Framework\Api\Search\SearchCriteriaBuilder?
Raphael bei Digital Pianism
1
Ich habe eine zufällige Kernklasse (keine API-Schnittstelle) ausprobiert und den gleichen Fehler erhalten: ReflectionException: Klasse Magento \ Bundle \ Model \ Sales \ Order \ Pdf \ Items \ ShipmentFactory existiert nicht
Fabian Schmengler
Was ist, wenn Sie es versuchen \Magento\Framework\App\Bootstrap::create(BP, $_SERVER)->getObjectManager()->create('\Magento\Framework\Api\Search\SearchCriteriaBuilderFactory');?
Raphael bei Digital Pianism
Interessant, das funktioniert, aber es scheint mir nicht richtig, den realen Objektmanager in Komponententests zu instanziieren (auch dieser Test wurde zehnmal langsamer gemacht) - ich hoffe, es gibt einen anderen Weg.
Fabian Schmengler
Ja, schlechte Idee. Was ist, wenn getObjectSie nicht anrufen getBuilder? Das sollte direkt über getObjectaber nur zum Testen geschehen .
Raphael bei Digital Pianism

Antworten:

7

Der einfachste Weg, damit umzugehen, besteht darin, die Kompilierung auszuführen, bevor Sie Tests ausführen:

bin/magento setup:di:compile

Die andere Möglichkeit besteht darin, Methoden für das Factory-Mock explizit zu definieren, z. anstatt dies zu tun:

$someFactoryMock = $this->getMockBuilder('Vendor\Module\Model\SomeFactory')
        ->disableOriginalConstructor()
        ->getMock();

Mach das:

$someFactoryMock = $this->getMockBuilder('Vendor\Module\Model\SomeFactory')
        ->disableOriginalConstructor()
        ->setMethods(['create'])
        ->getMock();

Irgendwann habe ich versucht, damit umzugehen, indem ich ObjectManager::getObjectvor dem Erstellen eines Mocks angerufen habe , aber dies scheint keine saubere Lösung zu sein. Eine andere Sache ist, dass es nicht geholfen hat - es hat ein Objekt erstellt, aber die Klasse in var / generation nicht gespeichert. Ich habe mich nicht mehr damit befasst.

Wojtek Naruniec
quelle
1
Ich habe diese Antwort akzeptiert, weil das Verspotten der Fabrik die eleganteste Lösung war und auch dann funktioniert, wenn sie nicht generiert wird.
Fabian Schmengler
5

Das Problem stammt aus der PHPUnit-Verspottungsbibliothek, da die benötigte Klasse nicht automatisch geladen werden kann.

Wenn Sie sich Magento dev repo ansehen, wird der Autoloader-Catcher eingerichtet, der auf Anforderung eine Klasse generiert. Wenn Sie eine ähnliche Bootstrap-Datei in Ihrem Modul-Repository erstellen, funktioniert dies recht gut: 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']);

Ich würde jedoch empfehlen, einen anderen Ansatz zu verwenden, indem Sie ein virtuelles Dateisystem verwenden, damit Ihre materialisierten generierten Klassen Ihren Build nicht beschädigen, wenn sich die Signatur der Schnittstelle der generierten Klassen ändert.

composer require --dev mikey179/vfsStream

Und dann in Ihrer Bootstrap-Datei:

$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']);

Ich habe einen ähnlichen Ansatz verwendet, als ich einen Adapter für PHPSpec https://github.com/EcomDev/phpspec-magento-di-adapter/blob/master/src/Extension.php#L98 erstellt habe

Ivan Chepurnyi
quelle
Hört sich toll an, ich werde es ausprobieren
Fabian Schmengler
4

Sie können auch so etwas verwenden

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;
}

und irgendwo im Code einfach weitergeben

class Some {
    __constructor(
        MyFactory $myFactory
      ){}
}

 $this->objectManagerHelper->getObject(Some::class,[
    'myFactory' => $this->getMockupFactory(My::class)
 ])
Eduard Melnyk
quelle
Ich habe eine Variation davon verwendet und sie passte perfekt zu meinem Anwendungsfall. Und ich mag es, dass es generisch ist, also kann ich jetzt jederzeit eine Fabrik verspotten.
Tbernard
Schöne Lösung. Möglicherweise möchten Sie das Verhalten ein wenig ändern, damit Sie eine haben $instanceNameund $factoryNamefalls Sie eine Schnittstellenfactory haben, von der erwartet wird, dass sie Datenmodelle zurückgibt.
Giel Berkers